При разработке в среде 1С часто возникает необходимость обработать большой массив данных в фоновом режиме. Иногда архитектура решения требует, чтобы задачи выполнялись строго по очереди, но с разными входными параметрами. Попытка запустить фоновые задания в обычном цикле на клиенте часто приводит к тому, что система либо запускает их параллельно (создавая избыточную нагрузку), либо прерывает выполнение предыдущего задания при старте нового. Давайте разберем, почему классический цикл не работает и какие профессиональные подходы существуют для решения этой задачи.
Рассмотрим типичную ошибку: программист пытается использовать цикл Для каждого на стороне клиента и внутри него вызывает фоновое задание, надеясь на использование стандартных механизмов ожидания БСП. Проблема заключается в том, что в управляемом приложении клиентский поток не может быть полностью заблокирован до завершения серверного процесса без нарушения логики работы интерфейса. Метод ДлительныеОперацииКлиент.ОжидатьЗавершения работает через механизм обратных вызовов (callback), а значит, код, следующий за вызовом этой процедуры в цикле, продолжит выполняться немедленно. В результате цикл «пролетает» до конца, пытаясь инициировать все задания сразу.
Проанализируем несколько стратегий, которые помогут обойти это ограничение и выстроить задачи в правильную очередь.
Как выяснилось в ходе обсуждения, наиболее надежным и простым решением является изменение логики самого фонового задания. Вместо того чтобы запускать процедуру 10 раз с разными параметрами, мы передаем в нее один раз массив или таблицу значений, содержащую все необходимые данные. Выясним, как это выглядит в коде:
На стороне клиента мы подготавливаем данные:
ПараметрыЗапуска = Новый Массив;
Для Каждого Строка Из ТаблицаДанных Цикл
ПараметрыЗапуска.Добавить(Строка.Значение);
КонецЦикла;
// Запускаем одно фоновое задание, передавая весь массив
ДлительныеОперации.ВыполнитьПроцедуруВФоне(УникальныйИдентификатор, "МойОбщийМодуль.ОбработатьСписокПараметров", ПараметрыЗапуска);
В общем модуле реализуем цикл уже внутри фонового процесса:
Процедура ОбработатьСписокПараметров(ПараметрыЗапуска) Экспорт
Для Каждого ТекущийПараметр Из ПараметрыЗапуска Цикл
// Выполняем полезную работу для каждого параметра последовательно
ОбработатьОдинЭлемент(ТекущийПараметр);
КонецЦикла;
КонецПроцедуры
Этот метод исключает накладные расходы на создание множества фоновых заданий и гарантирует, что данные будут обработаны строго друг за другом.
Если запуск цепочки заданий происходит не интерактивно пользователем, а, например, в другом фоновом или регламентном задании на стороне сервера, мы можем применить блокирующий метод ФоновыеЗадания.ОжидатьЗавершения(). В отличие от клиентских методов, серверный поток честно приостанавливает выполнение до получения результата. Рассмотрим пример:
Для Каждого Параметр Из МассивПараметров Цикл
// Запускаем фоновое задание
Задание = ФоновыеЗадания.Выполнить("МойОбщийМодуль.ВыполнитьЗадачу", Параметр);
// Помещаем задание в массив для ожидания
МассивЗаданий = Новый Массив;
МассивЗаданий.Добавить(Задание);
// Сервер ждет здесь до завершения (или таймаута)
Попытка
ФоновыеЗадания.ОжидатьЗавершения(МассивЗаданий);
// Здесь можно проанализировать результат выполнения
Исключение
ЗаписатьЛогОшибки("Задание завершилось с ошибкой");
КонецПопытки;
КонецЦикла;
В ситуациях, когда мы не хотим занимать серверный поток ожиданием (например, цепочка очень длинная), можно применить метод «передачи эстафеты». Суть заключается в том, что каждое фоновое задание в самом конце своей работы инициирует запуск следующего экземпляра самого себя с новым набором параметров.
Разберем логику реализации:
Процедура ВыполнитьЗадачуЭстафетой(МассивПараметров) Экспорт
Если МассивПараметров.Количество() = 0 Тогда
Возврат;
КонецЕсли;
ТекущийПараметр = МассивПараметров[0];
МассивПараметров.Удалить(0); // Убираем отработанный параметр
// Выполняем основную работу
ОбработатьДанные(ТекущийПараметр);
// Если остались параметры, запускаем следующее задание
Если МассивПараметров.Количество() > 0 Тогда
ФоновыеЗадания.Выполнить("ОбщийМодуль.ВыполнитьЗадачуЭстафетой", МассивПараметров);
КонецЕсли;
КонецПроцедуры
Если необходимо обновлять прогресс на форме и запускать задания по одному, мы должны отказаться от цикла Для каждого и использовать рекурсивный вызов через оповещение. Посмотрим на этот алгоритм:
ЗапуститьСледующееЗадание().ДлительныеОперацииКлиент) снова вызываем ЗапуститьСледующееЗадание().Проанализируем пример кода на форме:
&НаКлиенте
Процедура НачатьОбработку()
ЗаполнитьОчередьПараметров(); // Наполняем массив Объект.Очередь
ЗапуститьСледующееЗадание();
КонецПроцедуры
&НаКлиенте
Процедура ЗапуститьСледующееЗадание()
Если Объект.Очередь.Количество() = 0 Тогда
Сообщить("Все задачи выполнены!");
Возврат;
КонецЕсли;
ТекущийПараметр = Объект.Очередь[0].Значение;
Объект.Очередь.Удалить(0);
ПараметрыЗадания = Новый Структура("Параметр", ТекущийПараметр);
Результат = ФоновоеВыполнениеНаСервере(ПараметрыЗадания);
Оповещение = Новый ОписаниеОповещения("ЗавершениеЗадания", ЭтотОбъект);
ДлительныеОперацииКлиент.ОжидатьЗавершения(Результат.ИдентификаторЗадания, Оповещение);
КонецПроцедуры
&НаКлиенте
Процедура ЗавершениеЗадания(Результат, ДополнительныеПараметры) Экспорт
// Здесь обрабатываем результат текущего шага
// И запускаем следующий
ЗапуститьСледующееЗадание();
КонецПроцедуры
Если вы не можете менять код фонового задания, но хотите гарантировать, что одновременно работает только один экземпляр, используйте параметр Ключ при вызове ФоновыеЗадания.Выполнить(). Если в системе уже выполняется задание с таким же ключом, новое задание не будет запущено. Это позволяет организовать "мягкую" очередь: перед запуском проверять наличие активных заданий с данным ключом через ФоновыеЗадания.ПолучитьФоновыеЗадания(). Если массив не пуст — значит, нужно подождать (например, через ПодключитьОбработчикОжидания).
Резюме: Выбор метода зависит от ваших условий. Если параметры известны заранее — передавайте их массивом в одно задание (Способ 1). Если нужно управлять процессом с клиента и видеть прогресс — используйте цепочку оповещений (Способ 4). Если же запуск происходит в пакетном режиме на сервере — используйте метод ожидания (Способ 2).