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

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

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

Механизм свойства Ключ на уровне платформы

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

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

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

Программная проверка через список фоновых заданий

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


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

    // Основная логика задания
    // ...
    
КонецПроцедуры

Функция ФоновоеЗаданиеУжеЗапущено(ПараметрыЗадания)
    // Получаем список заданий, соответствующих параметрам
    МассивЗаданий = ФоновыеЗадания.ПолучитьФоновыеЗадания(ПараметрыЗадания);
    
    // Если найдено более одного активного задания (одно из них — текущее),
    // значит, другое уже выполняется.
    Если МассивЗаданий.Количество() > 1 Тогда
        Возврат Истина;
    КонецЕсли;
    
    Возврат Ложь;
КонецФункции

Этот метод кажется логичным, но у него есть два существенных недостатка. Во-первых, при огромном количестве выполненных заданий в истории базы метод ПолучитьФоновыеЗадания может работать медленно. Во-вторых, существует риск "состояния гонки" (Race Condition): если две копии задания стартуют в одну и ту же миллисекунду, обе функции ФоновоеЗаданиеУжеЗапущено могут вернуть Ложь, так как информация о новом задании еще не успела обновиться в реестре менеджера кластера.

Использование управляемых блокировок — самый надежный метод

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

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

  1. Создадим вспомогательный регистр сведений (например, СостоянияРегламентныхЗаданий), где в качестве измерения будет имя задания.
  2. В начале выполнения задания откроем транзакцию.
  3. Установим исключительную управляемую блокировку на запись в этом регистре.
  4. Если блокировка установлена успешно — продолжаем работу. Если система выдает ошибку блокировки — значит, другое задание уже удерживает этот ресурс.

Пример реализации:


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СостоянияРегламентныхЗаданий");
ЭлементБлокировки.УстановитьЗначение("ИмяЗадания", "ЗагрузкаДанных");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;

НачатьТранзакцию();
Попытка
    Блокировка.Заблокировать();
    // Если мы здесь, значит мы — единственный владелец блокировки.
    // Выполняем полезную нагрузку...
    
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    // Блокировка не удалась, значит задание уже выполняется в другом сеансе
    Возврат;
КонецПопытки;

Проблема "зависшего флага" и как её решить

Иногда разработчики используют обычную константу или флаг в регистре (например, булево Выполняется = Истина). Посмотрим на ситуацию критически: что произойдет, если в момент выполнения задания сервер "упадет" или процесс rphost будет принудительно завершен? Флаг останется в состоянии Истина навсегда, и задание больше никогда не запустится автоматически.

Чтобы этого избежать, проанализируем более сложную структуру флага. Вместо простого "да/нет" записывайте в регистр Дату и время начала, а также Номер сеанса (НомерСеанса). При старте нового задания проверяйте:

Идемпотентность — лучшая защита от дублей

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

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

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

← На главную