Как правильно организовать запуск регламентного задания по условию в 1С?

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

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

Естественным решением кажется использование регламентного задания, которое будет с заданной периодичностью обрабатывать накопившиеся ошибки. Однако возникает дилемма: стоит ли гонять регламентное задание «вхолостую» каждые 15 минут (или даже чаще), если ошибок нет? В этой статье мы подробно разберем, как правильно организовать такой процесс, проанализируем популярные подходы и выявим критические антипаттерны, которые могут навредить производительности вашей базы данных.

Вариант 1. Проверка наличия работы внутри регламентного задания (Классический подход)

Большинство опытных разработчиков сходятся во мнении, что самый простой и при этом самый надежный способ — это постоянно работающее регламентное задание, которое первым делом проверяет наличие записей в «очереди ошибок» (в специальном регистре сведений).

Рассмотрим, как выглядит типичная схема реализации этого подхода:

  1. При неудачной попытке отправки данных мы записываем ключ объекта (например, ссылку на документ или справочник) в регистр сведений ОшибкиОбмена.
  2. Регламентное задание запускается по расписанию (например, каждые 10–15 минут).
  3. Первой же строкой кода в обработчике регламентного задания мы выполняем легкий запрос к регистру сведений.
  4. Если регистр пуст, задание мгновенно завершает свою работу. Если записи есть — запускается логика повторной отправки.

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


Процедура ВыполнитьПовторнуюОтправкуДанных() Экспорт

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

КонецПроцедуры

Почему этот способ хорош: «Холостой» запуск такого задания на современном сервере СУБД занимает считанные миллисекунды. Запрос к индексированной таблице регистра сведений выполняется мгновенно и не создает никакой избыточной нагрузки на сервер. Механизм работает предсказуемо, прозрачно и полностью контролируется стандартным планировщиком 1С.

Вариант 2. Динамическое изменение активности регламентного задания (Антипаттерн)

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

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


// Включение регламентного задания при возникновении ошибки
РЗадание = РегламентныеЗадания.НайтиПредопределенное(Метаданные.РегламентныеЗадания.ПовторныйОбменДанными);
РЗадание.Использование = Истина;
РЗадание.Записать();

Внимание! Разберем, почему данный подход является опасным антипаттерном:

Вариант 3. Использование «Очереди заданий» БСП (Рекомендуемый стандарт)

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

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

Рассмотрим, как это работает на практике:

  1. При возникновении ошибки отправки вы не включаете глобальное регламентное задание, а просто регистрируете точечную задачу в системной очереди БСП.
  2. В параметрах задачи вы можете указать точное время ее отложенного старта (например, текущее время + 15 минут).
  3. Система сама выполнит это задание в фоновом режиме через общий пул потоков БСП.

Пример программного добавления задания в очередь БСП:


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

// Добавляем задание в общую очередь БСП
ОчередьЗаданий.Добавить(ПараметрыЗадания);

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

Вариант 4. Создание динамических (непредопределенных) регламентных заданий

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

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

  1. В конфигураторе создаем новое регламентное задание, но снимаем у него флаг Предопределенное.
  2. При возникновении ошибки в коде мы проверяем, создано ли уже динамическое задание для обработки этой очереди. Если нет — создаем его программно и задаем ему нужное расписание.
  3. Как только регламентное задание отработало и очистило всю очередь ошибок, оно само удаляет свой экземпляр из базы данных.

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


// Создание уникального экземпляра задания при появлении ошибки
ИмяЗадания = "ПовторныйОбмен_Группа_1";
Отбор = Новый Структура("Наименование", ИмяЗадания);
НайденныеЗадания = РегламентныеЗадания.ПолучитьРегламентныеЗадания(Отбор);

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

В конце успешного выполнения процедуры фонового процесса мы можем просто удалить это задание:


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

Вариант 5. Ручной запуск фонового задания по событию

Иногда требуется запустить обработку немедленно при записи определенного события, не дожидаясь расписания. Для этого можно использовать программный запуск метода через механизм фоновых заданий (ФоновыеЗадания.Выполнить). Чтобы избежать параллельного запуска дублирующих процессов, обязательно нужно выполнять проверку на то, не запущено ли уже аналогичное задание в данный момент.

Разберем пример реализации безопасного ручного старта:


&НаСервере
Процедура ЗапуститьПовторныйОбменВручную()

    ИмяМетода = Метаданные.РегламентныеЗадания.ШаблонПовторногоОбмена.ИмяМетода;
    
    // Проверяем, выполняется ли уже аналогичное фоновое задание в данный момент
    Отбор = Новый Структура;
    Отбор.Вставить("ИмяМетода", ИмяМетода);
    Отбор.Вставить("Состояние", СостояниеФоновогоЗадания.Активно);
    
    АктивныеЗадания = ФоновыеЗадания.ПолучитьФоновыеЗадания(Отбор);
    
    // Если активных заданий нет, запускаем новое
    Если АктивныеЗадания.Количество() = 0 Тогда
        НаименованиеЗадания = "Ручной запуск обмена данными";
        ФоновыеЗадания.Выполнить(ИмяМетода, , , НаименованиеЗадания);
    КонецЕсли;

КонецПроцедуры

Критическая ошибка: «Висящие» фоновые задания с бесконечным циклом

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

Почему это категорически запрещено делать:

Стратегия экспоненциальной задержки (Exponential Backoff)

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

Если внешний сервис упал, глупо отправлять ему запросы с одинаковой частотой — это может лишь усугубить ситуацию (вы устроите ему своеобразную DDoS-атаку после его поднятия), либо просто впустую потратит ресурсы вашего сервера. Разумнее увеличивать интервал между попытками:

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

← На главную