Как передать данные между обработчиками событий разных объектов в 1С и запустить фоновое задание?

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

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

Если перед нами стоит задача собрать список всех измененных товаров (например, для последующего обновления остатков на сайте или во внешней системе) во всех этих вызовах, возникает закономерный вопрос: где хранить этот массив данных, если стандартное свойство ДополнительныеСвойства принадлежит конкретному объекту и недоступно в обработчиках других объектов?

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

---

Вариант 1. Использование параметров сеанса

Параметры сеанса представляют собой глобальные переменные, которые живут на протяжении всего времени работы пользователя с базой данных. Они доступны в любом контексте (и в модуле документа, и в общих модулях, и в подписках на события).

Для реализации этого подхода выполним следующие шаги:

  1. В конфигураторе создадим новый параметр сеанса (например, ИзмененныйИмпортТоваров) с типом ФиксированныйМассив или ХранилищеЗначения (так как обычные массивы передавать напрямую в параметры сеанса в некоторых случаях нельзя из-за требований к сериализации).
  2. В самом первом вызове (например, в подписке на событие ПередЗаписью документа) инициализируем этот параметр сеанса.
  3. В последующих подписках на события регистров считываем текущее значение, добавляем туда новые данные и записываем обратно.

Недостатки метода:

---

Вариант 2. Общий модуль с повторным использованием значений (Кэш-модуль)

Один из самых элегантных способов сымитировать глобальную переменную "на время вызова сервера" — это использование общего модуля с включенным свойством Повторное использование возвращаемых значений.

Рассмотрим, как это работает на практике. Мы можем создать общий модуль КэшСобытийПроведения со свойствами:

Внутри модуля напишем функцию, которая возвращает мутабельное значение (например, структуру или соответствие):


Функция ПолучитьКонтейнерДанных() Экспорт
    Контейнер = Новый Структура;
    Контейнер.Вставить("МассивТоваров", Новый Массив);
    Возврат Контейнер;
КонецФункции

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

Пример использования в подписках:


// В обработчике ПередЗаписью набора записей регистра
Контейнер = КэшСобытийПроведения.ПолучитьКонтейнерДанных();
Для Каждого СтрокаЗаписи Из Источник Цикл
    Если Контейнер.МассивТоваров.Найти(СтрокаЗаписи.Номенклатура) = Неопределено Тогда
        Контейнер.МассивТоваров.Добавить(СтрокаЗаписи.Номенклатура);
    КонецЕсли;
КонецЦикла;

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

---

Вариант 3. Использование планов обмена

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

Разберем этот механизм по шагам:

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

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

---

Вариант 4. Таблица-очередь на базе регистра сведений

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

Создадим непериодический независимый регистр сведений ОчередьОбновленияОстатков со следующей структурой:

Схема работы выглядит следующим образом:


// В подписке ПриЗаписи набора записей регистра
НаборЗаписей = РегистрыСведений.ОчередьОбновленияОстатков.СоздатьНаборЗаписей();
Для Каждого Товар Из СписокИзмененныхТоваров Цикл
    НоваяЗапись = НаборЗаписей.Добавить();
    НоваяЗапись.Номенклатура = Товар;
    НоваяЗапись.ДатаИзменения = ТекущаяДатаСеанса();
КонецЦикла;
НаборЗаписей.Записать(Истина); // Перезаписываем данные, избегая дублей

Фоновое или регламентное задание периодически (например, раз в 10 секунд) считывает этот регистр, обрабатывает порцию товаров и очищает записи из регистра.

---

Важная проблема: Запуск фонового задания из транзакции

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

Выясним причину: фоновое задание запускается в отдельном сеансе на сервере. В тот момент, когда в первом сеансе выполняется транзакция (код внутри подписок), эти изменения еще не зафиксированы в СУБД. Запустившись параллельно, фоновое задание попытается прочитать движения документа или записи в регистре сведений и ничего там не обнаружит (либо наткнется на старые данные), так как для него эта транзакция еще "не существует".

Как решить эту проблему? Рассмотрим два наиболее эффективных паттерна.

Решение А. Паттерн "Управляемая блокировка в фоновом задании"

Этот метод позволяет синхронизировать работу фонового задания с моментом окончания транзакции в основном сеансе без использования периодических опросов базы данных.

Алгоритм работы:

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

Посмотрим на пример кода внутри фонового задания:


Процедура ВыполнитьОбработкуОстатков(СсылкаНаДокумент) Экспорт
    
    НачатьТранзакцию();
    
    // Накладываем управляемую исключительную блокировку на документ
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("Документ.РеализацияТоваровУслуг");
    ЭлементБлокировки.УстановитьЗначение("Ссылка", СсылкаНаДокумент);
    ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
    
    Попытка
        Блокировка.Заблокировать();
    Исключение
        // Обработка ошибки блокировки, если превышено время ожидания
        ОтменитьТранзакцию();
        Возврат;
    КонецПопытки;
    
    // Проверяем, существует ли документ в базе (на случай, если транзакция отменилась)
    Если ОбщегоНазначения.СсылкаСуществует(СсылкаНаДокумент) Тогда
        // Здесь выполняем чтение движений и отправку во внешнюю систему
        ОбработатьДвиженияДокумента(СсылкаНаДокумент);
    КонецЕсли;
    
    ЗафиксироватьТранзакцию();
    
КонецПроцедуры

---

Решение Б. Использование Очереди отложенной обработки (по стандартам БСП)

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

Для таких сценариев по стандартам разработки 1С рекомендуется использовать Очередь отложенной обработки:

Этот подход гарантирует стабильность системы при любых пиковых нагрузках и обеспечивает надежную передачу данных без потери транзакционной целостности платформы 1С.

← На главную