Как запустить фоновое задание после завершения транзакции проведения в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Часто при разработке в 1С мы сталкиваемся с необходимостью оптимизировать производительность системы. Если в обработчике ОбработкаПроведения выполняются "тяжелые" запросы или происходит массовое создание подчиненных документов, логичным шагом кажется вынести этот код в фоновое задание. Однако здесь мы наталкиваемся на серьезное архитектурное ограничение: фоновое задание запускает новый независимый сеанс базы данных, который из-за механизмов изоляции транзакций СУБД не видит изменения, еще не зафиксированные основной транзакцией. В результате фоновый сеанс обращается к базе, но видит пустой документ или старые данные табличных частей.

Давайте подробно разберем эту проблему и рассмотрим несколько рабочих архитектурных паттернов, которые помогут нам надежно и правильно выполнить фоновый код после завершения проведения документа.

Почему возникает проблема "невидимости" данных?

Платформа 1С:Предприятие устроена так, что обработчики ПередЗаписью, ПриЗаписи и ОбработкаПроведения всегда выполняются внутри единой транзакции базы данных. Принципиально отсутствует системный обработчик вроде ПриЗавершенииТранзакции. Когда мы внутри проведения запускаем фоновое задание, платформа стартует его немедленно, в отдельном сеансе. Основная транзакция в этот момент еще не зафиксирована (не сделан COMMIT), поэтому фоновый процесс не может прочитать новые данные. Для детального анализа порядка срабатывания системных процедур можно использовать инструмент для изучения последовательности событий 1С.

Проанализируем доступные нам решения, начиная от самых надежных архитектурных подходов и заканчивая техническими трюками.

Решение 1: Паттерн "Очередь заданий" через Регистр Сведений (Рекомендуется)

Это самый классический, надежный и архитектурно правильный подход, который гарантирует доставку и выполнение задания без коллизий. Разберем по шагам, как его реализовать:

  1. Создаем специальный служебный непериодический регистр сведений (например, ОчередьСозданияПодчиненныхДокументов), где измерением будет выступать ссылка на наш документ.
  2. Внутри процедуры ОбработкаПроведения мы просто формируем и записываем набор записей в этот регистр. Эта операция выполняется мгновенно и не тормозит работу пользователя.
  3. Суть транзакции: если документ проведется успешно, запись в регистре будет зафиксирована в базе. Если произойдет отказ проведения — транзакция откатится, и база данных сама удалит запись из регистра без нашего участия.
  4. Далее мы настраиваем регламентное задание, которое запускается по расписанию (например, раз в 10 секунд). Оно читает регистр сведений, берет порцию документов, выполняет для них тяжелые расчеты или создание подчиненных объектов, а после успешного выполнения — удаляет записи из очереди.

Таким образом мы полностью разделяем контексты, избавляемся от проблем с блокировками и гарантируем, что тяжелый код выполнится только для реально проведенных документов.

Решение 2: Использование стандартных очередей БСП

Если ваша конфигурация построена на современных версиях Библиотеки стандартных подсистем (БСП) или вы работаете в таких решениях, как 1С:ERP, 1С:Комплексная автоматизация или 1С:УТ, посмотрим на уже встроенные механизмы отложенного проведения.

В БСП реализована система очередей (оперативная и неоперативная). Оперативная очередь предназначена как раз для быстрых фоновых доработок. Платформа берет на себя ответственность за запуск заданий сразу после успешного завершения транзакции проведения. Вам достаточно зарегистрировать обработчик в подсистеме отложенного проведения, и система сама сформирует задание, не заставляя вас писать свои регистры сведений.

Решение 3: Трюк с программным созданием разового регламентного задания

Рассмотрим очень изящный технический паттерн. Платформа 1С имеет интересную особенность обработки программно созданных регламентных заданий. Если мы программно создадим регламентное задание внутри транзакции проведения, менеджер заданий платформы не запустит его до тех пор, пока эта транзакция не завершится успехом.

Как это работает:

  1. В ОбработкаПроведения мы программно создаем новое задание, привязанное к экспортной процедуре общего модуля.
  2. Мы не задаем ему регулярное расписание (или указываем использование один раз).
  3. Если документ успешно проведен, транзакция фиксируется, создание регламентного задания сохраняется в базе, и платформа тут же запускает его в фоне.
  4. Если документ не проведен (откат транзакции), создание регламентного задания отменяется платформой, и холостого запуска не происходит.

Решение 4: Транзакционная блокировка в фоновом задании

В обсуждении был предложен интересный вариант синхронизации двух сеансов с использованием управляемых блокировок. Проанализируем этот алгоритм:

  1. Из модуля проведения мы запускаем фоновое задание стандартным методом.
  2. Основной сеанс (проводящий документ) в этот момент неявно удерживает эксклюзивную транзакционную блокировку на записываемый объект базы данных.
  3. В фоновом задании мы начинаем новую транзакцию и явно пытаемся установить управляемую блокировку на ссылку документа методом Заблокировать().
  4. Поскольку блокировка занята основным сеансом, фоновое задание "повисает" в ожидании ее снятия (стандартный таймаут около 20 секунд).
  5. Как только транзакция проведения завершается (успехом или откатом), блокировка снимается. Фоновое задание немедленно получает управление.

Важный нюанс: транзакция могла завершиться откатом! Поэтому сразу после получения блокировки в фоновом задании необходимо проверить статус проведения документа (или сверить ВерсияДанных), чтобы не начать выполнять тяжелый код для документа, который так и не провелся.

Решение 5: Цикл с задержкой (Антипаттерн)

Наконец, посмотрим на решение, к которому временно прибегнул автор оригинального вопроса. Он использовал цикл с ожиданием, проверяя изменение версии данных объекта. Посмотрим на пример кода:


ЧислоПопыток = 0;
Пока ЧислоПопыток < 5 Цикл
    ЧислоПопыток = ЧислоПопыток + 1;    
    Если Версия = ДокументСсылка.ВерсияДанных Тогда   
        Прервать; // Изменения зафиксированы
    Иначе
        Если ЧислоПопыток = 5 Тогда
            Возврат; // Выход по таймауту
        КонецЕсли;  
        ОбщегоНазначенияБТС.Пауза(3); // Спим 3 секунды
    КонецЕсли;
КонецЦикла;

Почему это плохо: Данный подход считается ярко выраженным антипаттерном в профессиональной разработке 1С. Функция ОбщегоНазначенияБТС.Пауза() (или аналогичные вызовы) "усыпляет" рабочий процесс сервера. Фоновый сеанс висит, потребляя оперативную память и занимая пул соединений. Если пользователи начнут массово перепроводить документы (например, при групповом перепроведении), десятки таких спящих фоновых заданий мгновенно исчерпают лимит рабочих процессов сервера 1С, что приведет к жесткой деградации производительности всей системы.

Мы настоятельно рекомендуем использовать архитектурные подходы (Решение 1 или 2) для гарантированного и безопасного выполнения пост-обработки документов.

← На главную