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