В процессе автоматизации часто возникает задача: по некоторому событию в системе необходимо запустить длительную фоновую обработку. Классический пример — распределение поступившего товара по заказам клиентов. Проблема в том, что такие события могут происходить очень часто. Если новое поступление товара произойдет до того, как завершится распределение предыдущего, запуск второго экземпляра обработки параллельно с первым может привести к некорректным данным и ошибкам.
Стоит задача — организовать выполнение таких обработок строго последовательно. Если одна обработка уже запущена, все новые вызовы должны становиться в очередь и запускаться только после завершения текущей. Разберем несколько подходов к решению этой задачи, от самых надежных до более простых в реализации.
Это классический, наиболее надежный и управляемый способ организации очереди. Его суть заключается в том, чтобы не запускать фоновые задания напрямую из кода, а складывать информацию о необходимости их запуска в специальную таблицу в базе данных. А отдельный процесс — обработчик очереди — будет последовательно разбирать эти задачи.
Рассмотрим реализацию по шагам:
Создание хранилища очереди. В конфигурации создается объект для хранения задач, например, регистр сведений ОчередьФоновыхЗаданий. Он должен быть непериодическим, с режимом записи "Независимый".
Примерная структура измерений и ресурсов:
ИдентификаторЗадания (Измерение, тип УникальныйИдентификатор) — первичный ключ.ИмяМетода (Измерение, тип Строка) — имя процедуры, которую нужно выполнить.Состояние (Ресурс, тип Перечисление.СостоянияЗаданийОчереди) — со значениями "Новое", "ВОбработке", "Выполнено", "Ошибка".ДатаСоздания (Ресурс, тип Дата) — для сортировки и выбора самой старой задачи.Параметры (Ресурс, тип ХранилищеЗначения) — для передачи параметров в фоновое задание.ИнформацияОбОшибке (Ресурс, тип Строка) — для записи текста ошибки, если она произойдет.Постановка задачи в очередь. В том месте, где раньше происходил прямой вызов фонового задания (например, при проведении документа поступления), теперь нужно добавлять запись в созданный регистр сведений. Вместо ФоновыеЗадания.Выполнить(...) пишем код, который создает новую запись в регистре ОчередьФоновыхЗаданий с состоянием "Новое" и сохраняет все необходимые параметры в ресурсе Параметры.
Создание обработчика очереди. Создается регламентное задание (например, "РазборОчередиФоновыхЗаданий"), которое будет запускаться с небольшим интервалом, скажем, каждые 30-60 секунд. Это регламентное задание будет выполнять основную логику диспетчеризации.
Логика обработчика.
В процедуре, выполняемой регламентным заданием, реализуется следующий алгоритм:
ФоновыеЗадания.Выполнить(). В качестве параметров передаем ему идентификатор записи из регистра.Преимущества этого подхода:
Недостатки:
Если нет строгих требований к сохранности очереди при перезапуске сервера, а важна простота реализации без изменения структуры конфигурации, можно воспользоваться встроенными возможностями платформы 1С. Этот метод основан на анализе уже запущенных фоновых заданий.
Алгоритм действий следующий:
В коде, который должен выполняться в фоновом задании, в самом его начале, необходимо добавить блок проверки и ожидания.
С помощью метода ФоновыеЗадания.ПолучитьФоновыеЗадания() получаем коллекцию всех фоновых заданий на сервере.
В цикле перебираем эту коллекцию и ищем задания, которые соответствуют нашей обработке. Искать можно по свойству ИмяМетода или, что надежнее, по свойству Ключ, которое можно задать при запуске задания. Также важно проверять состояние задания — нас интересуют только активные (СостояниеФоновогоЗадания.Активно).
Необходимо исключить из поиска само текущее задание. Для этого можно сравнить свойство УникальныйИдентификатор.
Если в результате поиска найдено другое активное задание нашей обработки, это означает, что предыдущий экземпляр еще не завершил работу. В этом случае текущее задание должно встать в "паузу" и дождаться его окончания. Для этого используется метод НайденноеЗадание.ОжидатьЗавершения().
После того как ожидание завершится, текущее задание может продолжить свое выполнение.
Посмотрим на пример кода, который может быть в начале экспортной процедуры, вызываемой фоновым заданием:
Процедура ДлительнаяОбработкаНаСервере(Параметры) Экспорт
// Получаем собственное фоновое задание, чтобы знать свой ID
ТекущееЗадание = ФоновыеЗадания.ТекущееФоновоеЗадание();
Если ТекущееЗадание = Неопределено Тогда
// Это не фоновое задание, возможно, запуск в отладке.
// Продолжаем выполнение или вызываем исключение.
КонецЕсли;
// Ищем другие активные экземпляры этого же задания
Отбор = Новый Структура;
Отбор.Вставить("Ключ", "РаспределениеТоваровПоЗаказам"); // Используем одинаковый ключ для всех заданий
Отбор.Вставить("Состояние", СостояниеФоновогоЗадания.Активно);
НайденныеЗадания = ФоновыеЗадания.ПолучитьФоновыеЗадания(Отбор);
Для Каждого Задание Из НайденныеЗадания Цикл
// Пропускаем самих себя в списке
Если ТекущееЗадание <> Неопределено И Задание.УникальныйИдентификатор = ТекущееЗадание.УникальныйИдентификатор Тогда
Продолжить;
КонецЕсли;
// Нашли работающий экземпляр, ждем его завершения
Попытка
Задание.ОжидатьЗавершения();
Исключение
// Обрабатываем ситуацию, если задание было прервано администратором
ЗаписьЖурналаРегистрации("Ожидание фонового задания", УровеньЖурналаРегистрации.Ошибка, , , ИнформацияОбОшибке());
КонецПопытки;
КонецЦикла;
//
// ... Здесь начинается основная логика фоновой обработки ...
//
КонецПроцедуры
Преимущества этого подхода:
Недостатки:
Можно скомбинировать подход с базой данных, но избавиться от регламентного задания. Логика здесь сложнее и требует аккуратного обращения с блокировками.
Этот метод позволяет минимизировать количество запусков фоновых заданий и задержки, связанные с интервалом регламентного задания, но он значительно сложнее в реализации и отладке из-за необходимости корректно управлять блокировками для избежания "гонки состояний" (race condition).
Выбор способа организации очереди фоновых заданий зависит от требований конкретной задачи.
Для бизнес-критичных операций, где гарантия выполнения и сохранность очереди являются приоритетом (как в примере с распределением товаров), однозначно рекомендуется подход с использованием регистра сведений и регламентного задания-обработчика. Он надежен, прозрачен и хорошо масштабируется.
Для более простых сценариев, где нужно лишь предотвратить параллельное выполнение и потеря очереди при перезапуске сервера некритична, можно использовать "облегченный" метод с ПолучитьФоновыеЗадания() и ОжидатьЗавершения(). Он прост в реализации и не требует доработки конфигурации.
Также стоит помнить, что в типовых конфигурациях, разработанных на базе Библиотеки стандартных подсистем (БСП), уже могут существовать готовые механизмы очередей заданий (например, подсистема ОчередьЗаданий). Перед созданием собственного решения всегда полезно изучить имеющиеся в конфигурации стандартные инструменты.