При автоматизации рутинных операций в 1С часто возникает задача открыть сразу несколько окон одной и той же обработки для разных параметров. Например, нам необходимо открыть форму стандартной обработки «Клиент-Банк» (или «Обмен с банком») для нескольких организаций или банковских счетов одновременно, чтобы пользователь мог визуально проконтролировать процесс загрузки выписок.
Однако при попытке реализовать это простым циклом разработчики часто сталкиваются с неприятным поведением системы: открывается только первая форма, а на последующих строках кода на мгновение мелькает окно ошибки, после чего процесс прерывается, и новые окна закрываются. В этой статье мы подробно разберем, почему так происходит, какие системные механизмы платформы «1С:Предприятие» за это отвечают, и рассмотрим несколько эффективных способов обхода этой проблемы.
Проанализируем исходный код, который обычно приводит к подобной ошибке:
&НаКлиенте
Процедура КомандаЗагрузить(Команда)
нпп = 0;
Для каждого стр Из ТаблицаЗагрузить Цикл
нпп = нпп + 1;
Если стр.Надо Тогда
ОткрытьФормуОбработкиКлиентБанкЗагрузить(стр, нпп);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура ОткрытьФормуОбработкиКлиентБанкЗагрузить(стр, нпп)
СтруктураПараметров = Новый Структура;
СтруктураПараметров.Вставить("РежимПоУмолчанию", "ГруппаЗагрузка");
СтруктураПараметров.Вставить("Организация", стр.Организация);
СтруктураПараметров.Вставить("БанковскийСчет", стр.БанковскийСчет);
СтруктураПараметров.Вставить("ФайлЗагрузки", стр.Файл);
// Пытаемся открыть уникальную форму, используя нпп в качестве ключа уникальности
Форма1 = ОткрытьФорму("Обработка.КлиентБанк.Форма", СтруктураПараметров, , нпп, ВариантОткрытияОкна.ОтдельноеОкно, , , РежимОткрытияОкнаФормы.Независимый);
Форма1.Заголовок = "Счет " + стр.БанковскийСчет;
КонецПроцедуры
На первый взгляд, код написан абсолютно верно. Разработчик позаботился об уникальности окон на клиенте, передавая во второй параметр метода ОткрытьФорму (после структуры параметров) переменную нпп (номер по порядку) в качестве ключа уникальности. Это действительно гарантирует, что на клиенте платформа попытается создать новые независимые окна, а не активизировать уже открытое.
Тем не менее, возникает ошибка. Если поймать выполнение кода в режиме отладки на паузе или включить остановку по ошибке, мы увидим системное исключение: «Задание с таким значением ключа уже выполняется». Почему оно возникает, если мы открываем формы обработок?
Дело в том, что современный «Клиент-Банк» в типовых конфигурациях (например, в «1С:Бухгалтерии 3.0») — это сложный механизм. При инициализации формы (в обработчике ПриСозданииНаСервере или при открытии на клиенте) система автоматически запускает фоновые процессы. Это может быть:
Для выполнения этих тяжелых операций конфигурация использует метод ФоновыеЗадания.Выполнить(). Чтобы избежать дублирования одинаковых фоновых процессов в рамках одного сеанса пользователя, разработчики фирмы «1С» задают для этих заданий жесткий Ключ. Обычно этот ключ формируется как комбинация имени пользователя, организации и самой операции.
Когда мы запускаем цикл Для каждого, управление передается серверу практически мгновенно (в пределах миллисекунд). Первое окно открывается и успешно инициирует фоновое задание. Буквально через миллисекунду управление переходит ко второй строке цикла. Вторая форма пытается открыться и запускает точно такое же фоновое задание с тем же самым ключом на сервере. Платформа видит, что задание с таким ключом уже активно работает, и выбрасывает исключение блокировки. Так как это происходит в момент инициализации формы, её открытие аварийно прерывается, а клиентское окно моментально закрывается.
Попытки передать параметр Ключ в СтруктураПараметров при открытии формы обработки ни к чему не приведут. Этот параметр зарезервирован платформой только для форм объектов баз данных (справочников, документов) для указания ссылки на конкретный элемент, и игнорируется обычными обработками.
---
Если нам критически важно открыть именно интерактивные формы для пользователя и конфигурация находится на поддержке (мы не можем менять код самой обработки), самым простым и надежным решением будет использование обработчика ожидания. Мы откажемся от жесткого цикла и будем открывать формы последовательно с небольшой задержкой (например, в 1–2 секунды). За это время фоновое задание предыдущей формы успеет отработать и завершиться.
Реализуем этот алгоритм по шагам:
Посмотрим на пример реализации данного подхода:
&НаКлиенте
Перем ОчередьНаОткрытие; // Массив структур с данными для открытия
&НаКлиенте
Перем ТекущийИндексОчереди; // Число, индекс текущего элемента
&НаКлиенте
Процедура КомандаЗагрузить(Команда)
ОчередьНаОткрытие = Новый Массив;
ТекущийИндексОчереди = 0;
// Заполняем очередь только теми строками, которые выбрал пользователь
Для каждого стр Из ТаблицаЗагрузить Цикл
Если стр.Надо Тогда
ДанныеСтроки = Новый Структура;
ДанныеСтроки.Вставить("Организация", стр.Организация);
ДанныеСтроки.Вставить("БанковскийСчет", стр.БанковскийСчет);
ДанныеСтроки.Вставить("Файл", стр.Файл);
ОчередьНаОткрытие.Добавить(ДанныеСтроки);
КонецЕсли;
КонецЦикла;
// Если очередь не пуста, запускаем процесс циклического открытия
Если ОчередьНаОткрытие.Количество() > 0 Тогда
ПодключитьОбработчикОжидания("ОткрытьСледующуюФормуИзОчереди", 0.1, Истина);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ОткрытьСледующуюФормуИзОчереди()
Если ТекущийИндексОчереди >= ОчередьНаОткрытие.Количество() Тогда
// Все формы открыты, очищаем переменные
ОчередьНаОткрытие = Неопределено;
Возврат;
EndIf;
ТекущиеДанные = ОчередьНаОткрытие[ТекущийИндексОчереди];
СтруктураПараметров = Новый Структура;
СтруктураПараметров.Вставить("РежимПоУмолчанию", "ГруппаЗагрузка");
СтруктураПараметров.Вставить("Организация", ТекущиеДанные.Организация);
СтруктураПараметров.Вставить("БанковскийСчет", ТекущиеДанные.БанковскийСчет);
СтруктураПараметров.Вставить("ФайлЗагрузки", ТекущиеДанные.Файл);
// Используем индекс в качестве ключа уникальности формы
УникальныйКлючФормы = "КлиентБанк_" + СтрЗаменить(Строка(ТекущиеДанные.БанковскийСчет), " ", "");
Попытка
Форма1 = ОткрытьФорму("Обработка.КлиентБанк.Форма", СтруктураПараметров, , УникальныйКлючФормы, ВариантОткрытияОкна.ОтдельноеОкно, , , РежимОткрытияОкнаФормы.Независимый);
Если Форма1 <> Неопределено Тогда
Форма1.Заголовок = "Загрузка: " + ТекущиеДанные.БанковскийСчет;
КонецЕсли;
Исключение
// Записываем ошибку в Журнал регистрации, чтобы не прерывать весь процесс
ЗаписатьОшибкуВЛог(ОписаниеОшибки());
КонецПопытки;
// Переходим к следующему элементу
ТекущийИндексОчереди = ТекущийИндексОчереди + 1;
// Подключаем следующий шаг с небольшой задержкой (например, 1.5 секунды)
// Этого времени достаточно, чтобы серверное фоновое задание успело инициализироваться и завершиться
ПодключитьОбработчикОжидания("ОткрытьСледующуюФормуИзОчереди", 1.5, Истина);
КонецПроцедуры
---
Если ваша база данных позволяет вносить изменения или вы можете использовать механизм расширений, наиболее правильным с технической точки зрения решением будет устранение конфликта уникальности ключа самого фонового задания на сервере.
Для этого выполним следующие действия:
КлиентБанк на сервере. Нам нужно найти вызов метода ФоновыеЗадания.Выполнить().
КлючЗадания = "ОбменСБанком_" + Параметры.Организация;
// В модуле формы обработки КлиентБанк при создании на сервере:
&После("ПриСозданииНаСервере")
Процедура ИНФ_ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Получаем уникальный идентификатор текущей формы
ЭтотУникальныйИдентификатор = ПолучитьСсылкаНового(); // Или Строка(УникальныйИдентификатор)
// Сохраняем его в параметрах запуска фонового процесса
КонецПроцедуры
При генерации фонового задания добавим этот идентификатор в ключ запуска:
// Вместо старого ключа формируем новый, абсолютно уникальный для данного окна:
КлючЗадания = "ОбменСБанком_" + Параметры.Организация + "_" + Параметры.ИдентификаторФормы;
ФоновыеЗадания.Выполнить(ИмяМетода, ПараметрыВыполнения, КлючЗадания, "Загрузка данных банка");
Благодаря этому изменению платформа сможет параллельно запускать неограниченное количество фоновых заданий для разных окон, не конфликтуя между собой.
---
Давайте подумаем: насколько удобно пользователю работать, если на экране одновременно откроется 10, 15 или 20 окон обработки «Клиент-Банк»? Скорее всего, пользователю придется в каждом окне совершать однотипные клики мышкой (например, нажимать кнопку «Загрузить»). Это неэффективно и сильно перегружает оперативную память как клиентского компьютера, так и сервера приложений 1С.
Проанализируем архитектурную альтернативу: мы можем отказаться от интерактивного открытия форм и выполнить всю работу программно на стороне сервера. Для этого достаточно написать собственную небольшую процедуру, которая будет обращаться к объектной модели обработки КлиентБанк:
&НаСервере
Процедура ВыполнитьПрограммнуюЗагрузкуНаСервере(ТаблицаДанных)
Для каждого СтрокаДанных Из ТаблицаДанных Цикл
Если Не СтрокаДанных.Надо Тогда
Продолжить;
КонецЕсли;
// Создаем объект обработки программно, без визуализации
ОбъектОбработки = Обработки.КлиентБанк.Создать();
// Заполняем реквизиты обработки данными из нашей таблицы
ОбъектОбработки.Организация = СтрокаДанных.Организация;
ОбъектОбработки.БанковскийСчет = СтрокаДанных.БанковскийСчет;
ОбъектОбработки.ИмяФайлаЗагрузки = СтрокаДанных.Файл;
// Настраиваем параметры программного выполнения (зависит от логики конфигурации)
ОбъектОбработки.РежимРаботы = "Загрузка";
Попытка
// Вызываем экспортный метод загрузки, который обычно отрабатывает на сервере
ОбъектОбработки.ВыполнитьЗагрузку();
// Фиксируем успешный результат в лог
ЗаписатьИнформациюВЛог(СтрокаДанных.БанковскийСчет, "Успешно загружено");
Исключение
// Если произошла ошибка, фиксируем её и идем дальше по циклу
ЗаписатьОшибкуВЛог(СтрокаДанных.БанковскийСчет, ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
КонецПроцедуры
При таком подходе пользователь запускает процесс один раз нажатием одной кнопки. Система на сервере последовательно (или даже в один поток) обрабатывает все файлы выписок. В завершение процесса пользователю выводится лаконичный отчет: какие счета обработаны успешно, а по каким возникли ошибки (например, не найден файл выписки). Это экономит ресурсы, исключает возникновение блокировок фоновых заданий и существенно упрощает работу конечного пользователя.