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