Фоновые задания для новичков

26.01.26

Разработка - Механизмы платформы 1С

Если вы еще не знаете, с версии 8.3.26 произошло важное нововведение в работе с фоновыми заданиями в частности. Объясню, в чем дело, а заодно расскажу, как вообще работать с фоновыми заданиями на базовом уровне.

Файлы

ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.

Наименование Скачано Купить файл
Фоновые задания (демобаза)
.dt 108,08Mb
1 2 500 руб. Купить

Подписка PRO — скачивайте любые файлы со скидкой до 85% из Базы знаний

Оформите подписку на компанию для решения рабочих задач

Оформить подписку и скачать решение со скидкой

Оглавление:

1. Вступление

2. Кратко о фоновых заданиях

3. Простые примеры

4. Возвращение результатов выполнения функции

5. Нововведения 26-й версии

5.1 Необходимость проверки результата при использовании временного хранилища

6. (Не)точный таймер

7. Некоторые факты о фоновых заданиях

 

1. Вступление

Не люблю ждать, поэтому меня частенько тянет что-нибудь оптимизировать (ох, не знаю, каким словом меня вспоминают на прошлой работе, где я расширением выжимал из 10-15 секунд хотя бы 3, чтобы частая операция не связывалась с тормозами в работе, да и где только не оптимизировал). А когда нет возможности ускорить процесс, хочется перевести в фоновое задание. Ведь фоновое задание - это, считай сторонний рабочий, который делает нужную работу и сообщает о результатах. Только раньше о результатах нужно было постоянно спрашивать (готово ли), а с версии 8.3.26 можно просто подписаться на окончание процесса! Очень удобно, потому что мониторинг состояния фонового задания на управляемых формах невозможен без серверных вызовов, а это дергание интерфейса!

Другим назначением фонового задания может стать циклическая операция по обновлению информации, которая может затянуться по независящим от нас причинам. Кстати, подобная ситуация и побудила меня написать такую статью, потому что фоновые задания в очередной раз выручили, а в Руководстве разработчика Радченко про них ничего нет! (Если кому интересно, запрос, который в обычных условиях выполняется за 30 мс, в достаточно редкое время мог затянуться до 2 минут, отловить причину пока не удалось, да и вряд ли поможет)

 

2. Кратко о фоновых заданиях

Если обратиться к синтакс-помощнику, то мы обнаружим, что:

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

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

Что значит без ожидания завершения в пользовательском контексте - то, что можно будет заняться другим делом - перейти на другие вкладки, например, пока задание выполняется. Но как тогда получить результат выполнения? Смотри пункт 2, 4 и 5: мы можем получить список текущих заданий, и, зная реквизиты нужного нам, определить, завершено оно уже или нет. Если завершено, мы также можем получить результат его выполнения и уведомить пользователя, если нужно, о том, что задание выполнено.

Звучит неплохо. Но на самом деле кластер хранит информацию по крайней мере о 1000 последних выполняемых фоновых заданиях! Как нам идентифицировать наше? Для этого служат такие реквизиты задания, как то: УникальныйИдентификатор, Ключ и Наименование. Причем если первые два уже по названию должны быть уникальны в каком-то роде, то последнее позволяет нам, например:

1. выстраивать цепочки из заданий (пока выполняется одно задание с таким наименованием, не стартовать второе),

2. объединять задания, чтобы они выполнялись параллельно, а мы потом могли проверить, все ли выполнены и приступили к дальнейшим операциям,

3. ну или просто помечать задания для целей мониторинга, ведь наименование высвечивается в том числе в Журнале регистрации.

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

Что еще нужно знать о заданиях для успешного старта? По умолчанию задания принимают на вход только процедуры общих модулей, но в БСП разных версий есть процедуры, которые позволяют также обращаться и к другим методам, например, из менеджера объектов (ОбщегоНазначения.ВыполнитьМетодКонфигурации или ОбщегоНазначения.ВыполнитьБезопасно в зависимости от версии БСП) или даже метод Объекта (ОбщегоНазначения.ВыполнитьМетодОбъекта).

 

3. Простые примеры фоновых заданий

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

Функция ПогодаПоAPI(Широта, Долгота, ТокенДоступа)
	
	Заголовки = Новый Соответствие;
	Заголовки.Вставить("X-Yandex-Weather-Key", ТокенДоступа); //"1bd8d9d6-0ebb-4cb8-b59a-6d2e3f6c5a11");
	Соединение = Новый HTTPСоединение("api.weather.yandex.ru", 443, , , , 60, Новый ЗащищенноеСоединениеOpenSSL, Ложь);
	Запрос = Новый HTTPЗапрос("/v2/forecast?lat=" + Формат(Широта, "ЧРД=.") + "&lon=" + Формат(Долгота, "ЧРД=."), Заголовки);
	Ответ = Соединение.Получить(Запрос);
	Если Ответ.КодСостояния = 200 Тогда
		ОтветСтрокой = Ответ.ПолучитьТелоКакСтроку();
	ИначеЕсли НРег(ТокенДоступа) = "тестовый" Тогда
		Макет = Обработки.ДемонстрацияФоновыхЗаданий.ПолучитьМакет("ТестоваяПогода");
		ОтветСтрокой = Макет.ПолучитьТекст();
	Иначе	
		ОбщегоНазначения.СообщитьПользователю("Не настроен доступ к сервису прогноза погоды");
		Возврат Неопределено;
	КонецЕсли;
	Возврат ОтветСтрокой;
	
КонецФункции
Для получения информации о погоде соединяемся с сервером Яндекс, в заголовки вставляем ключ доступа из личного кабинета, в текст запроса - широту и долготу с точкой вместо запятой (дробная часть). Запрос необходимо запускать с методом GET, поэтому используем метод Получить. Если ответ успешен (КодСостояния 200), то получаем текст ответа, иначе на случай окончания доступа или отсутствия профиля Яндекс оставлен один из полученных ранее ответов (берем из макета).

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

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

Обратите внимание! Мы в зависимости от успешности прочтения в обоих случаях используем СообщениеПользователю. Если вас занесет в функции БСП, то вы узнаете, что Сообщения там используются, например, для передачи клиенту прогресса выполнения функции, а у нас все проще. Фоновое задание должно передать ответ клиенту, который его вызвал, но напрямую это невозможно, ведь мы не дожидаемся завершения фонового задания, а поля РезультатВыполнения у объекта Фоновое задание пока не придумали) Зато есть хорошая функция ПолучитьСообщенияПользователю, чем мы и воспользуемся.

Итак, функцию получения погоды мы подготовили, а теперь первый вызов фонового задания с нашей формы:

ПараметрыФункции = Новый Массив;
ПараметрыФункции.Добавить(Широта);
ПараметрыФункции.Добавить(Долгота);
ПараметрыФункции.Добавить(ТокенДоступа);
	
Задание = ФоновыеЗадания.Выполнить("ПогодаСервер.ТекущаяПогода", ПараметрыФункции, , "Текущая погода");
Возврат Задание.УникальныйИдентификатор;

Мы формируем простой массив, в который в порядке перечисления в сигнатуре функции включены все параметры, как то: Широта, Долгота и ТокенДоступа. Пропустить какой-то параметр посередине, чтобы присвоилось значение по умолчанию здесь не получится, увы (даже если указать код ПараметрыФункции.Добавить() без указания элемента, этот параметр будет присвоен в Неопределено). Далее вызываем функцию менеджера фоновых заданий Выполнить, где первым параметром идет вызываемый метод, далее наш сформированный массив параметров. Обратите внимание, что модуль называется ПогодаСервер. Дело в том, что фоновые задания запускаются всегда только на сервере, не могут в тонком клиенте, поэтому модуль используем серверный! Третьим параметром ничего не передаем, ключ не нужен. Ну и наименование для отметки в журнале регистрации.

Интересно, что же мы получаем в качестве результата запуска:

 

Свойства и методы объекта Фоновое задание, возвращаемого функцией Выполнить

 

Видим здесь уже известные нам ИмяМетода, Ключ, Наименование. Также идет интересный любителям регистрировать все исключения стандартный объект ИнформацияОбОшибке. Начало и Конец как временные указатели работы задания. Ну и конечно же Состояние, по которому можно определить выполнилось ли задание и его идентификатор для быстрого доступа. Его запомним в небольшую табличку, которую для начала будем обрабатывать в Обработчике ожидания:

 
 Мониторинг получения погоды (проверяем фоновые задания)

Итак, как мы получаем результат выполнения фонового задания? А делаем мы простую по сути вещь:
1. мы ищем в списке заданий непроверенное на завершение задание и читаем его сообщения (Задание.ПолучитьСообщенияПользователю(Истина)).
2. Если задание относится к определению текущей погоды, забираем текст сообщения, в котором, как мы помним, хранится ответ сервера Яндекс и распознаем его в Структуру
3. заполняем поля описания и передаем их в макет ПогодаСервер.ВывестиПогоду(ОписаниеПогоды, Погода);. Получаем такую картинку:

 

Текущая погода

 

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

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

Процедура ПерепроведениеДокументов(СписокДокументов, РежимПроведения) Экспорт
	
  Для Каждого Запись Из СписокДокументов Цикл
    Объект = Запись.Значение.ПолучитьОбъект();
    Если Объект.Проведен Тогда
      Попытка
        Объект.Записать(РежимЗаписиДокумента.Проведение, РежимПроведения);
        Сообщить("Документ """ + Объект.Ссылка + """ перепроведен");
      Исключение
        Сообщить("Документ """ + Объект.Ссылка + """ не был проведен по причине: "
           + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
      КонецПопытки;
    Иначе
      Сообщить("Документ """ + Объект.Ссылка + """ не был проведен, поэтому пропущен");
    КонецЕсли;
  КонецЦикла;
	
КонецПроцедуры

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

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

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

 

 

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

 

4. Возвращение результатов выполнения фоновой функции

Как мы уже знаем, фоновые задания не возвращают результата выполнения, можно прочитать только отстуканные там сообщения. Конец главы... Шутка! Конечно, разработчики 1С предоставили нам скрытую возможность получить результат некоим способом. Для того, чтобы добраться до истины, достаточно прочитать примечание синтакс-помощника по функции ПоместитьВоВременноеХранилище:

Примечание:

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

Данные, помещенные во временное хранилище в фоновом задании, не будут доступны из родительского сеанса до момента завершения фонового задания.

Идем использовать все с той же погодой. Ведь вы же не думали, что получение ответа сервера из сообщений - это предел мечтаний? Нет. Сперва пишем вызов:

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

Обратите внимание! Передача уникального идентификатора формы вторым параметром при помещении в хранилище крайне важна, поскольку без нее мы получим наш результат только первый раз за запущенный сеанс, далее, несмотря на завершение задания, мы получим то же Неопределено, что туда положили в клиентском сеансе! Также заметьте, что возвращается функцией уже структура из Адреса результата и идентификатора фонового задания, поскольку второй все так же нужен для мониторинга состояния, а первый - для получения результата.

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

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

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

// Запись - строчка нашей таблицы с заданиями
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Запись.ИдентификаторЗадания);
//...
РазметкаПогоды = ПолучитьИзВременногоХранилища(Запись.АдресРезультата);
ПогодаНаЗавтра = РазметкаПогоды;

Здорово, правда?! Только хотелось бы отметить один нюанс, на который вы могли не обратить внимание при чтении примечания из синтакс-помощника: "данные доступны только по завершении фонового сеанса". Ага, понятно, значит, если уверен, что данные будут всегда помещены, можно не запоминать идентификатора задания, а просто с периодичностью проверять, не появились ли данные во временном хранилище? Можно, но КРАЙНЕ не желательно. Потому что получить эти данные вы можете обычно только по истечении минуты-другой (!) после завершения задания (обычно, но не всегда - иногда работает быстро, у меня вначале быстро приходили данные, потом медленно). Так что же делать? А все просто: чтобы данные быстро перенеслись в клиентский сеанс, нужно всего лишь проверить состояние фонового задания по тому самому идентификатору или ключу/наименованию. Тогда данные переносятся мгновенно! Поэтому строчки три. Ну и разумеется, не надейтесь, что вы сможете несколько раз помещать что-то в фоновое задание частями (читайте синтакс-помощник).

 

5. Нововведения версии 8.3.26

Итак, как мы работали с фоновыми заданиями до этого: вызываем фоновое задание через менеджер, а потом либо ждем его завершения (метод ОжидатьЗавершенияВыполнения - правда непонятно, зачем в фоновое отправлять, если только не сразу в несколько параллельно), либо запускаем обработчик ожидания и проверяем состояние фонового задания. Есть еще вариант с ручной паузой, но он, судя по методу ВызватьПаузу, совсем не рекомендуется. А чем плохи обработчики ожидания, спросите Вы? А тем, что они в обычном случае мешают вводу, поскольку ваш обработчик должен обращаться к серверу, чтобы получить информацию о фоновом задании, а вызов сервера, это прерывание ввода. На форме демонстрации фоновых заданий это видно. Попробуйте вводить что-нибудь в поля широты и долготы, пока выполняется запрос на завтрашнюю погоду (там пауза для длительности) и увидите. Хотя возможно, я просто не знаю какого-то секрета - тогда, если несложно, просветите.

Чтобы не ждать в общем случае, когда же выполнится фоновое задание, разработчики в новой версии предусмотрели обращение с сервера к клиенту! И это без всякой системы взаимодействий, как раньше можно было! А чтобы раскрыть потенциал полностью в объект фонового задания добавили новый реквизит: НомерРодительскогоСеанса, который позволяет отправлять вызов напрямую тому сеансу, который вызвал это фоновое задание. Делается это все через специальный менеджер УведомленияКлиента, у которого есть 3 метода:

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

2. ОтправитьУведомление - доступен с сервера, используется, например, в фоновом задании, чтобы по тому же ключу отправить, внимание, ДАННЫЕ, которые, конечно же, должны быть доступны с клиента (никаких ТаблицЗначения и прочего). Третьим параметром идет массив с номерами сеансов-получателей, как получить по крайней мере один - я уже писал выше.

3. ОтключитьОбработчик - доступен с клиента, чтобы прекратить подписку на уведомления сервера. Нужен, чтобы серверу не нужно было обрабатывать лишние данные, а вам освободиться от последующих вызовов процедуры из ОписанияОповещения.

Для того, чтобы проверить работу, загрузим что-нибудь из интернета, например, классификатор банков:

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

Что интересного в данном коде? Самое интересное, как всегда, в конце. Поскольку вызывать данную функцию мы можем как из фонового задания, так и напрямую, надо обеспечить возможность проверки, как запущена функция. Для этого используем метод Текущего сеанса информационной базы "ПолучитьФоновоеЗадание", который возвращает Неопределено либо Объект ФоновоеЗадание. В первом случае просто возвращаем результат, ведь это обычный вызов, а во втором нам объект ФоновоеЗадание и понадобится, поскольку нам нужен номер сеанса, куда отправлять уведомление.

Поскольку ДанныеПоБанкам - это массив структур, то мы можем передать его в качестве данных. Вообще, хотелось бы отметить, что массив структур в большинстве случаев - это хорошая замена для небольших объемов информации. Но только для небольших. Если вы имеете дело с сотней-другой тысяч строк, готовьтесь к огромным утечкам памяти (10ГБ не предел и , как вы понимаете, х86 выдаст "Недостаточно памяти"). Также он неудобен в случае, когда данных нет совсем. Ведь пустая таблица содержит колонки, а пустой массив - нет и восстановить исходную таблицу для дальнейшего использования не получится. Если не хочется преобразования в строку можно воспользоваться функциями преобразования в массив массивов, но нужно быть готовым к тому, что не все значения объекта Тип поддерживаются на клиенте, поэтому их не должно быть в списке колонок, как и в значениях (в большинстве случаев это так). Приведу здесь свои примеры:

 
 Функции преобразования таблицы из/в поддерживаемый на клиенте объект

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

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

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

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

Обратите внимание, что в описании оповещения мы передаем дополнительным параметром структуру с Идентификатором задания. Но в данном случае мы это делаем не только для красоты, чтобы сделать запись о завершении фонового задания, но и для того, чтобы получить доступ к его данным (ведь мы помним, что пока состояние фонового задания не прочитано, данные из хранилища за короткое время не получить). По получении уведомления (мы больше не ждем ничего, ура!) пользователю выдается оповещение, которым он может запустить ответственную операцию обновления, также становится доступной кнопка обновления, если они, конечно, есть. Результат можно получить, например, такой (можно было, конечно, вывести таблицу, но что-то я в БСП не нашел формы для быстрого вывода ТЗ):

/********************************* НОВЫЕ БАНКИ **********************/
НаселенныйПункт: г. Барнаул, БИК: 040173745, Наименование: ""СИБСОЦБАНК" ООО"
НаселенныйПункт: г. Барнаул, БИК: 040173771, Наименование: "ООО КБ "АЛТАЙКАПИТАЛБАНК""
НаселенныйПункт: г. Барнаул, БИК: 040173604, Наименование: "АЛТАЙСКОЕ ОТДЕЛЕНИЕ N8644 ПАО СБЕРБАНК"
/*************************** ОБНОВЛЕННЫЕ БАНКИ **********************/
НаселенныйПункт: г. Астрахань, БИК: 041203001, Наименование старое: Ваш банк, новое - "ОКЦ № 3 ЮГУ Банка России"
НаселенныйПункт: г. Благовещенск, БИК: 041012718, Наименование старое: Вай, какой банк, новое - "ПАО КБ "ВОСТОЧНЫЙ""
НаселенныйПункт: г. Волгоград, БИК: 041806715, Наименование старое: Банк вашего соседа, новое - "Южный ф-л ПАО "Банк ПСБ""
/**************** БАНКИ, ПОМЕЧЕННЫЕ ВВИДУ ОТСУТСТВИЯ ***************/
НаселенныйПункт: , БИК: 040708778, Наименование: "ФКБ "РУССКИЙ ТРАСТОВЫЙ БАНК" Г. ЖЕЛЕЗНОВОДСК", ликвидирован 16.03.2010
НаселенныйПункт: , БИК: 040147746, Наименование: "ОАО КБ "РЕГИОНАЛЬНЫЙ КРЕДИТ"", ликвидирован 09.07.2009
НаселенныйПункт: , БИК: 040349823, Наименование: "КРАСНОДАРСКИЙ ФКБ "МОСКОВСКИЙ КАПИТАЛ" (ООО)", ликвидирован 25.07.2009

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

Также отмечу, что применение Уведомлений клиента безусловно шире, чем фоновые задания и вам никто не запретит использовать его в любых других сценариях, где нужно предупредить пользователя и предоставить ему выбор действий (возможно). Например:
- Процедура закрытия месяца завершена. Сформировать шахматку за декабрь?
- Через личный кабинет оформлен новый заказ по закрепленному за вами контрагенту - открыть его?
- Ирина закончила работу над планом производства. Перейти к формированию плана закупок?
- Документ "План закупок ЧС000023322 от 12.10.2025", который вы пытались открыть ранее, свободен - открыть его?...

5.1 Необходимость проверки результата при использовании временного хранилища

Если вы, как и я, попробуете возвращать через уведомления клиента не значение, а адрес временного хранилища, где оно хранится, чтобы, например, прочитать его на сервере, где доступно больше типов, то должен вас кое о чем предупредить. Дело в том, что по крайней мере на моем рабочем сервере, такая связка несколько раз в день возвращает Неопределено. Я имею ввиду, что в фоновом задании мы поместили значение в хранилище, чтобы прочитать его в клиентском сеансе, затем в последнем проверили, что задание завершилось (Задание.ОжидатьЗавершенияВыполнения) и пытаемся прочитать значение по переданному адресу. Но его там может не быть. Иногда, несмотря на всю стабильность данной связки (проверял на 100 запускаемых заданий каждые десять секунд в то время как компьютер под стресс-тестом - ни одной ошибки), на выходе получаем Неопределено, которое положили туда в клиентском сеансе. Причем в течении 1-3 минут ситуация обычно исправляется и значение таки появляется, но обычно это слишком долго. Поэтому передавать лучше то, что можно прочитать на клиенте либо быть готовым, что из временного хранилища будет получено Неопределено. Проблема проявлялась на версии 8.3.27.1859, на других не знаю, повторить не удается.

6. (Не)Точный таймер

Все, наверное, знают, что 1С не рекомендует использовать обработчики ожидания в качестве таймера, и, в общем смысле, таймер получается не очень точный - например, при ожидании минуту на простых операциях без значительной нагрузки отклонение будет составлять не менее секунды за 10 запусков (можете проверить). Казалось бы причем здесь фоновые задания? А при том, что мы теперь можем запустить задание-таймер, которое через определенные промежутки времени будет отправлять уведомление клиенту и он сможет запустить повторяющуюся процедуру! Идея вроде хорошая, по крайней мере от обработчиков ожидания уходим, но приходим к чему? Верно, к методу ВызватьПаузу, поскольку в фоновых заданиях в качестве таймера ожидания выступает он. И как у него с точностью спросите вы? Тоже неважно. До этого я проверял его на промежутках в пределах секунды и выяснил, что точность составляет до 10 мс. Но на больших промежутках отклонение даже больше, чем у обработчиков ожидания - 4 секунды за 10 замеров по минуте! Но свою роль он выполняет, не сомневайтесь: ожидание всегда отклоняется в большую сторону, то есть меньше по времени вы никогда ждать не будете, это главное. Да и потом, это отклонение достаточно просто прогнозируется - у него почти прямая зависимость от промежутка ожидания и достаточно подкорректировать метод пауза и можно запускать наш ручной таймер:

 
 Попытка в точность паузы

То есть мы вычитаем из паузы среднее отклонение, помноженное на интервал и благодаря этому достигаем точности 0,26 сек за 10 запусков и 0,35 - за 30, то есть за полчаса! Но точность такого таймера для кого-то может нивелироваться тем, что иногда будут проявляться более короткие промежутки ожидания, чем заданные, например, 59917 вместо 60000 мс. Вот такая вот идея ручного таймера в фоновом задании, реализацию можно посмотреть в демобазе (она немного усложнена ввиду желания передавать сообщения из фонового задания).

Не используйте метод ручного таймера в файловой базе, в ней он приводит к непредсказуемому поведению других фоновых заданий! 

7. Общие сведения о фоновых заданиях

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

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

 2. Уникальность по ключу и методу фиксируется в разрезе всех пользователей. То есть, если один пользователь запустил задание с ключом "Проверка" и методом "ОбщийМодуль.Проверка", то второй при запуске того же получит исключение

3. запуск сеанса фонового задания не занимает клиентскую лицензию (у меня как-то был случай с 30000 фоновых заданий на комьюнити-лицензии) и проходит по сокращенному сценарию, поэтому запуск занимает на среднестатистическом сервере порядка 0.1 секунды. Что очень хорошо по сравнению с тем же COM, но нужно понимать, что операция, которую вы хотите увести в фон, должна занимать значительно больше.

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

5. В БСП и раньше были функции-обертки для работы с фоновыми заданиями, но в последних версиях появилось нечто действительно примечательное: это многопоточное исполнение функций (!) и процедур, причем все это можно отслеживать через отдельный регистр. Единственное, что меня смутило, так это подготовка порций - они как-будто одиночные, хотя хотелось бы регулировать размер порции, использовать транзакции, да и смотри пункт 3. Я эти функции тогда еще не видел (их не было), так что разрабатывал свои - вот там это регулируется. (может будет статья, все таки такой простой, но достаточно продвинутой многопоточности пока не видел в статьях).

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

Вариант = 9;
Если Вариант = 0 Тогда
    ПараметрФункции = Новый УникальныйИдентификатор;
ИначеЕсли Вариант = 1 Тогда
    ПараметрФункции = Объект.ТабличнаяЧасть.Выгрузить();
ИначеЕсли Вариант = 2 Тогда
    ПараметрФункции = Новый ХранилищеЗначения(Неопределено);
ИначеЕсли Вариант = 3 Тогда
    ПараметрФункции = ТипЗнч(ПараметрФункции);
ИначеЕсли Вариант = 4 Тогда
    ПараметрФункции = Новый Картинка(ПолучитьДвоичныеДанныеИзСтроки("Не картинка"));
ИначеЕсли Вариант = 5 Тогда
    ПараметрФункции = Новый Файл("https://its.1c.ru/db/v8std#content:469:hdoc:1.2");
ИначеЕсли Вариант = 6 Тогда
    ПараметрФункции = ТекущийЭлемент;
ИначеЕсли Вариант = 7 Тогда
    ПараметрФункции = Новый СхемаЗапроса;
    ПараметрФункции.УстановитьТекстЗапроса("ВЫБРАТЬ &Параметр КАК Результат");
ИначеЕсли Вариант = 8 Тогда
    ПараметрФункции = Новый ФорматированныйДокумент;
    ПараметрФункции.УстановитьHTML("<h1>Пример работы</h1>", Новый Структура);
ИначеЕсли Вариант = 9 Тогда
    Запрос = Новый Запрос("ВЫБРАТЬ ПЕРВЫЕ 100 Истина КАК Результат");
    РезультатЗапроса = Запрос.Выполнить();
    ПараметрФункции = РезультатЗапроса;
КонецЕсли;

Здесь 10 вариантов. Сможете догадаться, какие 3 не могут быть переданы в фоновое без проверки? Один вариант сразу приоткрою - к сожалению, в фоновое задание нельзя передать форму и ее элементы. Зато все очень хорошо у системы компоновки. В общем случае для проверки сериализуемых объектов можно воспользоваться поиском в справке по слову "Сериализуется".

7. Фоновому заданию можно передать до 1 ГБ данных (если сомневаетесь, сколько вы передаете, можете попробовать сериализовать в строку - все прояснится), если информации будет больше, получите исключение. Кстати, я проверил, как много информации можно передать через УведомленияКлиенту - похоже, очень много. По крайней мере, я проверял и на 1.02 ГБ - приходит стабильно, а вот работа через временное хранилище показала себя еще более успешной. Кроме того, что через временное хранилище можно передать большее количество типов (например, через уведомление не передать Уникальный идентификатор, не знаю почему), скорость работы через него показывает прирост скорости в разы на больших объемах. Так, этот файл на 1.02 ГБ читался - передавался, проверялся на длину строки просто через уведомление 1 минуту 35 секунд, а вот передав адрес временного хранилища, те же действия я получил всего за 16 секунд! Подозреваю, что без сжатия не обошлось. В демобазе есть пример десериализации таблицы с множеством типов (пустых) значений, полученной из архива, которая в распакованном виде занимает более 1.17 ГБ и в таком и передается, можете узнать сколько строк и колонок туда вместилось.
В приведенном выше пунктом примере также передается большой объем информации (это первые два тома произведения "Война и мир"), как вы можете заметить, происходит это в считанные мгновения.

Для тех, кто использует 1C:EDT, подготовлен проект с демонстрационной обработкой на gitflic, который легко найти по фразе "фоновые задания", для всех остальных выгрузка демобазы с данными для проверки по условной цене.

Если вы еще не используете фоновые задания, то надеюсь, после этой статьи вы найдете им применение, как я в свое время. Спасибо за внимание!

Проверено на следующих конфигурациях и релизах:

  • 1С:Библиотека стандартных подсистем, редакция 3.1, релизы 3.1.11.335

Вступайте в нашу телеграмм-группу Инфостарт

й

См. также

Механизмы платформы 1С Программист Бесплатно (free)

Разберем 15 мифов о работе платформы «1С:Предприятие 8» – как распространенных, так и малоизвестных. Начнем с классики: «Код, написанный в одну строку, работает быстрее, чем многострочный». Так ли это на самом деле?

16.07.2025    27702    TitanLuchs    106    

147

Механизмы платформы 1С Работа с интерфейсом Программист Стажер 1С:Предприятие 8 Бесплатно (free)

Про ООП в 1С и о том, как сделать свой код более кратким и выразительным при помощи использования текучего интерфейса (fluent interface).

03.02.2025    15210    bayselonarrend    127    

68

Механизмы платформы 1С Программист 1С:Предприятие 8 Бесплатно (free)

В этой статье подробно рассматривается работа с JSON в XDTO в 1С:Предприятие. Вы узнаете, как сериализовать и десериализовать объекты XDTO в JSON, интегрировать 1С с веб-сервисами и API, а также корректно обрабатывать данные при обмене. Разбираются особенности работы с коллекциями, использование функций восстановления и частые ошибки при работе с JSON и XDTO.

30.01.2025    17276    user2122906    9    

61

Механизмы платформы 1С WEB-интеграция Программист 1С:Предприятие 8 Бесплатно (free)

В платформе 8.3.27 появилась возможность использовать WebSocket-клиент. Давайте посмотрим, как это все устроено и чем оно нам полезно.

14.01.2025    27895    dsdred    79    

143

Механизмы платформы 1С Программист Стажер 1С:Предприятие 8 1C:Бухгалтерия Бесплатно (free)

Эта небольшая статья - некоторого рода шпаргалка по файловым потокам: как и зачем с ними работать, какие преимущества это дает.

23.06.2024    25428    bayselonarrend    22    

175

Механизмы платформы 1С Программист Стажер 1С:Предприятие 8 1C:Бухгалтерия Бесплатно (free)

Пример использования «Сервисов интеграции» без подключения к Шине и без обменов.

13.03.2024    13883    dsdred    22    

85
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. paulwist 13.01.26 11:40 Сейчас в теме
По оформлению.

Только хотелось бы отметить один ньюанс,


Поправьте слово "ньюанс" на нюанс :)
2. kuzyara 2228 15.01.26 08:26 Сейчас в теме
Как сделать паузу в фоновом задании:
// Предоставляет функционал паузы в 1С, длительностью до 20 секунд
Функция Ожидать(ВремяОжиданияВСекундах) Экспорт
    
    Если ВремяОжиданияВСекундах<> 0 Тогда
        
        НастройкиПрокси = Новый ИнтернетПрокси(Ложь);
        НастройкиПрокси.НеИспользоватьПроксиДляЛокальныхАдресов = Истина;
        НастройкиПрокси.НеИспользоватьПроксиДляАдресов.Добавить("127.0.0.0");
        
        Попытка
            Замыкание = Новый HTTPСоединение(
                "127.0.0.0",,,,НастройкиПрокси,
                ВремяОжиданияВСекундах);
            Замыкание.Получить(Новый HTTPЗапрос());
        Исключение
            Возврат Неопределено;
        КонецПопытки;
        
    КонецЕсли;
    
КонецФункции
Показать
3. n_mezentsev 80 15.01.26 08:37 Сейчас в теме
(2) Я в курсе такого метода, сам использую его на клиенте. А вы замеряли точность этого метода? В рамках хотя бы 10-20 замеров по минуте, скажем... Не думаю, что у него очень высокая точность.
4. Avatarzorro 69 16.01.26 04:05 Сейчас в теме
(2) так сделали же метод в платформе
ВызватьПаузу()
. Он как раз был сделан для фоновых заданий
6. kuzyara 2228 16.01.26 06:18 Сейчас в теме
(4) И правда, с 8.3.25+, но метод куцый, с ограниченниями
Метод ВызватьПаузу доступен только на сервере и может быть вызван из фоновых заданий, веб-сервисов, http-сервисов, ботов и т.п.

Важная особенность: метод ВызватьПаузу недоступен в клиент-серверном вызове
8. Avatarzorro 69 16.01.26 06:24 Сейчас в теме
(6) ну так я и написал что он только для фоновых заданий по факту
9. n_mezentsev 80 16.01.26 06:25 Сейчас в теме
(6)
 Если ФоновоеЗадание = Неопределено Тогда 
...
ФоновоеЗадание = ФоновыеЗадания.Выполнить("ДлительныеОперацииСервер.Пауза", Параметры, , "Курить");
       Смещение = (ТекущаяУниверсальнаяДатаВМиллисекундах() - Отметка) / 1000;
       Если Смещение + 0.1 < Секунд Тогда
         ФоновоеЗадание.ОжидатьЗавершенияВыполнения(Секунд - Смещение);
       КонецЕсли;
...

Вот этот блок как раз для обычных серверных вызовов, но я и не настаиваю на использовании этой паузы, это просто ради фана, получится точной или нет
7. n_mezentsev 80 16.01.26 06:22 Сейчас в теме
(4) Ага, сделан. Только он неточный по времени, я его использую с корректировкой для лучшего результата)
5. Avatarzorro 69 16.01.26 04:09 Сейчас в теме
чет странно. у тебя указано что тестилось на бсп, но ничего из бсп ты не используешь...
10. n_mezentsev 80 16.01.26 06:30 Сейчас в теме
(5) ОбщегоНазначения.ВыполнитьМетодКонфигурации же из БСП, так что без него не обошлось. Вообще делаю напрямую через функции глобального контекста потому что мне так проще и гибче можно использовать (плюс работаю на устаревшей БСП, там меньше полезного). Да и зная их использование, уже понятнее как использовать БСП, потому что принципы общие.
11. Cyberhawk 137 27.01.26 17:49 Сейчас в теме
в один момент не могут быть запущены два задания с одним ключом и одинаковым выполняемым методом
К сожалению могут, если первое ФЗ запущено программно как обычное ФЗ, а второе стартовало само (по расписанию) через РЗ...
12. n_mezentsev 80 31.01.26 08:41 Сейчас в теме
(11) Я попробовал, да действительно фоновые задания отдельно, регламентные отдельно. Честно говоря, я вообще не понял, зачем нужен ключ регламентного задания кроме того, что он высвечивается в журнале регистрации: два регламентных задания с одним ключом прекрасно стартуют вместе, даже если у них боже упаси один метод. Непонятно, зачем в справке написано про последовательный запуск. Так что регламентные точно отдельная история...
Для отправки сообщения требуется регистрация/авторизация