Фоновые задания
Иногда требуется выполнить какую-нибудь длительную операцию без блокирования интерфейса. То есть пользователь нажал кнопку, запустилась какая-нибудь длительная процедура, а в этом время пользователь может делать что-нибудь другое в программе. Для этого можно использовать механизм фоновых заданий.
Фоновые задания запускаются
асинхронно (без ожидания завершения), в отдельном потоке. Фоновые задания можно запустить только на сервере. Для примера, представим что есть некий список электронных адресов, на который нужно выполнить рассылку по электронной почте. В списке может быть много адресов, да и отправка одного письма выполняется какое-то время. Поэтому хотелось бы запустить рассылку и продолжить дальше работать с программой.
Чтобы запустить фоновое задание используется метод Выполнить менеджера фоновых заданий. Первым параметром передается имя общего модуля и имя метода. Указанный метод должен находиться в общем модуле и быть экспортным.
ИмяМетода = "МодульФоновыхЗаданий.ВыполнитьРассылку";
ФоновыеЗадания.Выполнить(ИмяМетода);
//код из серверного общего модуля МодульФоновыхЗаданий
Процедура ВыполнитьРассылку() Экспорт
//код для выполнения рассылки
КонецПроцедуры
В результате будет запущено фоновое задание, которое выполнит метод ВыполнитьРассылку из общего модуля МодульФоновыхЗаданий. Во время выполнения фонового задания интерфейс пользователя не будет заблокирован.
Если сейчас трижды запустить одно фоновое задание, то оно будет запущено три раза и три раза выполнит одно и то же действие. Чтобы избежать повторного запуска фонового задания, если оно уже было запущено можно использовать Ключ фонового задания. Ключ передается третьим параметром в метод Выполнить:
ИмяМетода = "МодульФоновыхЗаданий.ВыполнитьРассылку";
Ключ = "Рассылка";
ФоновыеЗадания.Выполнить(ИмяМетода,, Ключ);
В результате при первом выполнении данного кода будет запущено фоновое задание с ключом «Рассылка». Если не дождавшись выполнения текущего фонового задания попробовать запустить его еще раз, то будет выброшено исключение «Задание с таким значением ключа уже выполняется». Если указать другое значение ключа, то оба фоновых задания будут выполняться одновременно.
Если у фоновых заданий с одним значением ключа не совпадают методы для выполнения, то такие задания могут быть запущены одновременно.
Стоит отметить, что в качестве метода для фонового задания можно использовать как процедуру, так и функцию. Возвращаемый результат функции будет проигнорирован. В качестве метода фонового задания нельзя использовать методы глобальных общих модулей.
Параметры фонового задания
При запуске фонового задания можно передать параметры в метод общего модуля. Например, добавим в процедуру ВыполнитьРассылку два параметра:
Процедура ВыполнитьРассылку(ПервыйПараметр, ВторойПараметр) Экспорт
//код для выполнения рассылки
КонецПроцедуры
При запуске фонового задания вторым параметром нужно перадать
массив параметров. Первый элемент массива будет передан в первый параметр, второй - во второй и т.д.
ИмяМетода = "МодульФоновыхЗаданий.ВыполнитьРассылку";
Ключ = "Рассылка";
//параметры
МассивПараметров = Новый Массив;
МассивПараметров.Добавить("Первый параметр");
МассивПараметров.Добавить("Второй параметр");
ФоновыеЗадания.Выполнить(ИмяМетода, МассивПараметров, Ключ);
Если количество параметров не будет совпадать, то фоновое задание не будет выполнено.
Фоновые задания в файловом и клиент-серверном варианте
Механизм выполнения фоновых заданий отличается в файловом и клиент-серверном варианте.
В файловом варианте фоновые задания выполняются на том клиентском приложении, которое его запустило. Одновременно может выполняться только одно фоновое задание, остальные фоновые задания встают в очередь.
В клиент-серверном варианте фоновые задания выполняются планировщиком заданий, который находится в менеджере кластера. Планировщик заданий подбирает наименее загруженный рабочий процесс сервера 1С и выполняет фоновое задание на нем.
И в файловом и клиент-серверном варианте фоновое задание это отдельный системный сеанс, который выполняется от имени того пользователя, кто создал задание.
Фоновое задание с ожиданием результата
Если требуется дождаться результата выполнения фонового задания, то можно воспользоваться тем, что метод Выполнить возвращает объект Фоновое задание. А у этого объекта есть метод ОжидатьЗавершенияВыполнения, который остановит поток выполнения, пока не изменится состояние фонового задания. Фоновое задание может быть в четырех состояниях:
- Активно - задание запущено на выполнение
- Завершено - задание успешно выполнено
- Завершено аварийно - возникла ошибка при выполнении задания
- Отменено - задание было отменено
После изменения состояния фонового задания метод ОжидатьЗавершенияВыполнения вернет обновленное фоновое задание, у которого можно анализировать свойство Состояние, чтобы узнать результат выполнения:
ИмяМетода = "МодульФоновыхЗаданий.ВыполнитьРассылку";
Ключ = "Рассылка";
//параметры
МассивПараметров = Новый Массив;
МассивПараметров.Добавить("Первый параметр");
МассивПараметров.Добавить("Второй параметр");
ФонЗад = ФоновыеЗадания.Выполнить(ИмяМетода, МассивПараметров, Ключ);
//останавливаем поток до окончания выполнения фонового задания
ФонЗад = ФонЗад.ОжидатьЗавершенияВыполнения();
//анализируем состояние фонового задания
Если ФонЗад.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
Сообщить("Выполнено успешно");
ИначеЕсли ФонЗад.Состояние = СостояниеФоновогоЗадания.ЗавершеноАварийно Тогда
Сообщить("Ошибка при выполнении " + ФонЗад.ИнформацияОбОшибке.Описание);
ИначеЕсли ФонЗад.Состояние = СостояниеФоновогоЗадания.Отменено Тогда
Сообщить("Задание отменено");
КонецЕсли;
Метод Выполнить вернул фоновое задание, состояние которого было равно Активно. Состояние автоматически не обновляется. Для проверки изменения состояния фонового задания нужно заново получить фоновое задание. В данном примере оно было получено методом ОжидатьЗавершенияВыполнения.
Пауза через фоновое задание
В метод ОжидатьЗавершенияВыполнения параметром можно передать количество секунд. Выполнение кода продолжится или по истечении этого времени или сразу после изменения состояния фонового задания. Можно воспользоваться данным методом для возможности оставить выполнение кода на указанное количество секунд. В общий модуль добавим следующую процедуру:
Процедура Пауза(Таймаут) Экспорт
ТекСеанс = ПолучитьТекущийСеансИнформационнойБазы();
ФонЗад = ТекСеанс.ПолучитьФоновоеЗадание();
//в сеансе фонового задания в переменной ФонЗад будет само фоновое задание
//а в клиентском сеансе будет Неопределено
Если ФонЗад = Неопределено Тогда
МассивПараметров = Новый Массив;
МассивПараметров.Добавить(Таймаут);
ФонЗад = ФоновыеЗадания.Выполнить("МодульФоновыхЗаданий.Пауза", МассивПараметров);
КонецЕсли;
ФонЗад.ОжидатьЗавершенияВыполнения(Таймаут);
КонецПроцедуры
Параметром принимаем количество секунд для паузы, затем определяем чей это сеанс. Если клиентский, то запускаем фоновое задание и ждем его завершения. Если это сеанс фонового задания, то просто ждем его завершения.
Вызов паузы на 5 секунд:
МодульФоновыхЗаданий.Пауза(5);
Преимущество такого метода в том, что на время паузы не загружается процессор.
Оповещение о выполнении фонового задания
Иногда требуется не просто запустить выполнение фонового задания, а оповестить пользователя о результатах выполнения. Для этого можно использовать следующий алгоритм действий:
1. Запускаем фоновое задание и сохраняем его уникальный идентификатор
2. С определенной периодичностью находим фоновое задание по идентификатору с помощью метода НайтиПоУникальномуИдентификатору и проверяем его состояние
//переменная модуля формы для сохранения
//идентификатора фонового задания
&НаКлиенте
Перем УИД;
&НаКлиенте
Процедура Команда1(Команда)
//запускаем фоновое задание
УИД = ЗапуститьФоновоеЗадание();
//подключаем обработчик ожидания
ПодключитьОбработчикОжидания("ПроверитьВыполнение", 1);
КонецПроцедуры
&НаСервере
Функция ЗапуститьФоновоеЗадание()
ИмяМетода = "МодульФоновыхЗаданий.ВыполнитьРассылку";
Ключ = "Рассылка";
//параметры
МассивПараметров = Новый Массив;
МассивПараметров.Добавить("Первый параметр");
МассивПараметров.Добавить("Второй параметр");
ФонЗад = ФоновыеЗадания.Выполнить(ИмяМетода, МассивПараметров, Ключ);
//возвращаем идентификатор фонового задания
Возврат ФонЗад.УникальныйИдентификатор;
КонецФункции
//данная процедура вызывается платформой раз в секунду
&НаКлиенте
Процедура ПроверитьВыполнение() Экспорт
Если ЗаданиеВыполнено(УИД) Тогда
//оповещаем пользователя
ПоказатьОповещениеПользователя("Задание выполнено");
//и отключаем обработчик ожидания
ОтключитьОбработчикОжидания("ПроверитьВыполнение");
КонецЕсли;
КонецПроцедуры
&НаСервере
Функция ЗаданиеВыполнено(УИД)
//находим фоновое задание по идентификатору
ФонЗад = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(УИД);
//проверяем статус
Если ФонЗад.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
Возврат Истина;
ИначеЕсли ФонЗад.Состояние = СостояниеФоновогоЗадания.Активно Тогда
//еще не выполнилось
Возврат Ложь;
Иначе
//отменено или была ошибка
Возврат Истина;
КонецЕсли;
КонецФункции
Здесь был использован метод ПодключитьОбработчикОжидания, который вызывает процедуру, указанную в первом параметре через время, указанное во втором параметре. Затем вызов процедуры был отменен через метод ОтключитьОбработчикОжидания.
Отмена выполнения фонового задания
Для отмены фонового задания можно использовать метод Отменить самого фонового задания:
ИмяМетода = "МодульФоновыхЗаданий.ВыполнитьРассылку";
Ключ = "Рассылка";
//параметры
МассивПараметров = Новый Массив;
МассивПараметров.Добавить("Первый параметр");
МассивПараметров.Добавить("Второй параметр");
//запускаем
ФонЗад = ФоновыеЗадания.Выполнить(ИмяМетода, МассивПараметров, Ключ);
//отменяем
ФонЗад.Отменить();
Сообщения из фонового задания
Фоновое задание выполняется на сервере, поэтому если в нем использовать сообщения пользователю, то их никто не увидит. Однако у фонового задания есть метод ПолучитьСообщенияПользователю, который позволяет получить все сообщения из фонового задания. Для примера добавим в общий модуль следующую процедуру:
Процедура СообщенияИзФона() Экспорт
Сообщить("Сообщение из фонового задания");
КонецПроцедуры
Выполним ее через фонового задание с получением всех сообщений и выведем полученные сообщения на экран:
ИмяМетода = "МодульФоновыхЗаданий.СообщенияИзФона";
Ключ = "СообщенияИзФона";
ФонЗад = ФоновыеЗадания.Выполнить(ИмяМетода,, Ключ);
ФонЗад.ОжидатьЗавершенияВыполнения();
//параметр Истина удаляет все полученные сообщения
СообщенияИзФона = ФонЗад.ПолучитьСообщенияПользователю(Истина);
Для Каждого Сообщ Из СообщенияИзФона Цикл
Сообщ.Сообщить();
КонецЦикла;
Через сообщения можно организовать получение прогресса выполнения фонового задания. Добавим в общий модуль следующие процедуры:
Процедура СообщенияИзФона() Экспорт
Для Сч = 1 По 10 Цикл
Сообщить(Сч);
Пауза(1);
КонецЦикла;
КонецПроцедуры
Процедура Пауза(Таймаут) Экспорт
ТекСеанс = ПолучитьТекущийСеансИнформационнойБазы();
ФонЗад = ТекСеанс.ПолучитьФоновоеЗадание();
//в сеансе фонового задания в переменной ФонЗад будет само фоновое задание
//а в клиентском сеансе будет Неопределено
Если ФонЗад = Неопределено Тогда
МассивПараметров = Новый Массив;
МассивПараметров.Добавить(Таймаут);
ФонЗад = ФоновыеЗадания.Выполнить("МодульФоновыхЗаданий.Пауза", МассивПараметров);
КонецЕсли;
ФонЗад.ОжидатьЗавершенияВыполнения(Таймаут);
КонецПроцедуры
И запустим фоновое задание на выполнение, с периодическим получением сообщений:
//переменная модуля формы для сохранения
//идентификатора фонового задания
&НаКлиенте
Перем УИД;
//процент выполнения задания
&НаКлиенте
Перем ПроцентВыполнения;
&НаКлиенте
Процедура Команда1(Команда)
//запускаем фоновое задание
УИД = ЗапуститьФоновоеЗадание();
//подключаем обработчик ожидания
ПодключитьОбработчикОжидания("ПроверитьВыполнение", 1);
КонецПроцедуры
&НаСервере
Функция ЗапуститьФоновоеЗадание()
ИмяМетода = "МодульФоновыхЗаданий.СообщенияИзФона";
Ключ = "СообщенияИзФона";
ФонЗад = ФоновыеЗадания.Выполнить(ИмяМетода,, Ключ);
//возвращаем идентификатор фонового задания
Возврат ФонЗад.УникальныйИдентификатор;
КонецФункции
//данная процедура вызывается платформой раз в секунду
&НаКлиенте
Процедура ПроверитьВыполнение() Экспорт
Если ЗаданиеВыполнено(УИД, ПроцентВыполнения) Тогда
//оповещаем пользователя
ПоказатьОповещениеПользователя("Задание выполнено");
//и отключаем обработчик ожидания
ОтключитьОбработчикОжидания("ПроверитьВыполнение");
Иначе
//выводим процент выполнения фонового задания
Состояние("Задание выполняется", ПроцентВыполнения*10);
КонецЕсли;
КонецПроцедуры
&НаСервере
Функция ЗаданиеВыполнено(УИД, ПроцентВыполнения)
//находим фоновое задание по идентификатору
ФонЗад = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(УИД);
//проверяем статус
Если ФонЗад.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
Возврат Истина;
ИначеЕсли ФонЗад.Состояние = СостояниеФоновогоЗадания.Активно Тогда
//получаем сообщения
СообщенияИзФона = ФонЗад.ПолучитьСообщенияПользователю(Истина);
Для Каждого Сообщ Из СообщенияИзФона Цикл
ПроцентВыполнения = Число(Сообщ.Текст);
КонецЦикла;
Возврат Ложь;
Иначе
//отменено или была ошибка
Возврат Истина;
КонецЕсли;
КонецФункции
В результате в процессе выполнения фонового задания можно будет увидеть окно состояния с прогрессом выполнения:
Многопоточность через фоновые задания
С помощью фоновых заданий можно выполнить какую-то обработку в несколько потоков. Например, нужно во всем справочнике товаров увеличить цену на 10%. Можно разбить все товары на несколько частей и каждую часть обработать в отдельном потоке:
Процедура МногопоточнаяОбработка() Экспорт
ЗапросТоваров = Новый Запрос;
ЗапросТоваров.Текст = "ВЫБРАТЬ
| Товары.Ссылка КАК Ссылка
|ИЗ
| Справочник.Товары КАК Товары
|ГДЕ
| НЕ Товары.ЭтоГруппа";
МассивТоваров = ЗапросТоваров.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка");
КоличествоТоваров = МассивТоваров.Количество();
КоличествоПотоков = 4;
РазмерПорции = Цел(КоличествоТоваров/КоличествоПотоков);
МассивЗаданий = Новый Массив;
Для Сч = 0 По КоличествоПотоков-1 Цикл
Если Сч = КоличествоПотоков-1 Тогда
НачИнд = Сч * РазмерПорции;
КонИнд = КоличествоТоваров-1;
Иначе
НачИнд = Сч * РазмерПорции;
КонИнд = Сч * РазмерПорции + РазмерПорции-1;
КонецЕсли;
ИмяМетода = "МодульФоновыхЗаданий.ИзменитьЦену";
МассивПараметров = Новый Массив;
МассивПараметров.Добавить(МассивТоваров);
МассивПараметров.Добавить(НачИнд);
МассивПараметров.Добавить(КонИнд);
МассивПараметров.Добавить(1.1);
ФонЗад = ФоновыеЗадания.Выполнить(ИмяМетода, МассивПараметров);
МассивЗаданий.Добавить(ФонЗад);
КонецЦикла;
Если МассивЗаданий.Количество() > 0 Тогда
ФоновыеЗадания.ОжидатьЗавершения(МассивЗаданий);
КонецЕсли;
КонецПроцедуры
Процедура ИзменитьЦену(МассивТоваров, НачИнд, КонИнд, Наценка) Экспорт
Для Сч = НачИнд По КонИнд Цикл
ТоварОбъект = МассивТоваров[Сч].ПолучитьОбъект();
ТоварОбъект.Цена = ТоварОбъект.Цена * Наценка;
ТоварОбъект.ОбменДанными.Загрузка = Истина;
ТоварОбъект.Записать();
КонецЦикла;
КонецПроцедуры
Здесь мы использовали метод ОжидатьЗавершения, но уже не у самого фонового задания, а у менеджера фоновых заданий. Параметром передали массив тех фоновых заданий, завершение которых нужно дождаться. У менеджера фоновых заданий тоже есть метод ОжидатьЗавершенияВыполнения, в который помимо массива заданий можно передать таймаут, аналогично одноименному методу у фонового задания. Даже если в методе ОжидатьЗавершенияВыполнения не указывать таймаут, то он в любом случае вернет массив всех обновленных фоновых заданий, после изменения статуса хотя бы у одного задания. Так как нам нужно дождаться завершения всех заданий, то был использован метод ОжидатьЗавершения.
Стоит отметить, что в одном фоновом задании можно запустить другое фоновое задание. То есть можно было запустить одно фоновое задание, в нем распараллелить выполнение на четыре фоновых задания, дождаться их выполнения и оповестить пользователя о завершении.
В силу ограничений выполнения фоновых заданий в файловом варианте многопоточную обработку данных можно реализовать только в клиент-серверном варианте.
Получить фоновые задания
Метод ПолучитьФоновыеЗадания позволяет получить список всех фоновых заданий, сохраненных в базе данных. Выполненные фоновые задания хранятся в течении суток, но не более 1000 заданий. Соответственно метод ПолучитьФоновыеЗадания вернет как текущие фоновые задания (выполняются в данный момент), так и завершившиеся в течении суток задания.
ВсеЗадания = ФоновыеЗадания.ПолучитьФоновыеЗадания();
Для Каждого Задание Из ВсеЗадания Цикл
Сообщить(Задание.Наименование);
Сообщить(Задание.Состояние);
КонецЦикла;
Нужно учитывать, что сюда также попадут системные фоновые задания, которые выполняет сама платформа.
Параметром можно передать структуру с отбором, чтобы получить только нужные фоновые задания:
//только активные фоновые задания
ОтборЗаданий = Новый Структура;
ОтборЗаданий.Вставить("Состояние", СостояниеФоновогоЗадания.Активно);
ВсеЗадания = ФоновыеЗадания.ПолучитьФоновыеЗадания(ОтборЗаданий);
//задания запущенные после 1 мая 2021
ОтборЗаданий = Новый Структура;
ОтборЗаданий.Вставить("Начало", Дата(2021,5,1));
ВсеЗадания = ФоновыеЗадания.ПолучитьФоновыеЗадания(ОтборЗаданий);
//с отбором по имени метода
ОтборЗаданий = Новый Структура;
ОтборЗаданий.Вставить("ИмяМетода", "МодульФоновыхЗаданий.СообщенияИзФона");
ВсеЗадания = ФоновыеЗадания.ПолучитьФоновыеЗадания(ОтборЗаданий);
Отбор можно устанавливать по следующим свойствам:
- УникальныйИдентификатор
- Ключ
- Состояние
- Начало
- Конец
- Наименование
- ИмяМетода
- РегламентноеЗадание
Можно сразу по нескольким свойствам.
Смотрите также:
Электронный учебник по программированию в 1С Рекомендации по изучению программирования 1С с нуля Игра "Кто хочет стать миллионером?" с вопросами на определенную тематику (язык программирования JavaScript, английские, немецкие, французские, испанские, португальские, нидерландские, итальянские слова, электробезопасность, промышленная безопасность, бокс и т.п.), написанная на 1С Программирование в 1С 8.3 с нуля - краткий самоучитель Комплексная подготовка программистов 1С:Предприятие 8.2 Сайты с уроками программирования и со справочниками Youtube-каналы с уроками программирования Сайты для обучения программированию Лекции и уроки