Отслеживание выполнения фонового задания

Публикация № 1106171

Разработка - Практика программирования

129
Запуск фонового задания из модуля внешней обработки. Отслеживание выполнения задания в виде прогресса, расположенного на форме.

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

Ниже несколько ссылок по фоновому выполнению кода:

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

Второй и третий варианты хорошие, в них используется функционал БСП для отслеживания хода выполнения задания:

НастройкиОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтаФорма);
НастройкиОжидания.ВыводитьПрогрессВыполнения = Истина;
НастройкиОжидания.ВыводитьСообщения = Истина;
НастройкиОжидания.ТекстСообщения = НСтр("ru = 'Выполняется обработка данных.'");
ДлительныеОперацииКлиент.ОжидатьЗавершение(ДлительнаяОперация, Неопределено, НастройкиОжидания);

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

Возьмем понемногу из всех вариантов, описанных выше, и получим следующее решение:

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

Для запуска фонового задания используем метод из БСП ДлительныеОперации.ВыполнитьВФоне.

Для отслеживания хода выполнения создадим на форме обработки два элемента управления: Прогресс и Тестовое поле.

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

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

Это можно сделать несколькими способами:

  1. Через механизм сообщений – самый простой и распространенный способ, который используют методы из БСП.
  2. Через сервер взаимодействия – вариант не плохой, но сложный. Нужно разворачивать сам сервер взаимодействия. Понятно, что только для получения информации из фоновых заданий, это делать смысла нет.
  3. Через временное хранилище – в некоторых источниках описывается данный вариант взаимодействия, но он работает только для файловых баз. В клиент-серверном режиме работы, получить данные из временного хранилища можно только после завершения фонового задания. Это нам не подходит.   
  4. Через хранилище общих настроек – я не тестировал данный вариант, но в статье про менеджер потоков судя по всему используется именно этот вариант.

Будем пользоваться первым способом используя методы БСП:

  • ДлительныеОперации.СообщитьПрогресс
  • ДлительныеОперации.ПрочитатьПрогресс
  • ДлительныеОперации.СообщенияПользователю

Ниже привожу тексты модуля обработки и модуля формы:

Обработка выполнения в фоне

 

Тексты процедур модуля обработки

Функция СведенияОВнешнейОбработке() Экспорт
   ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1");
   ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиДополнительнаяОбработка();
   НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
   НоваяКоманда.Представление = "Форма запуска задания";
   НоваяКоманда.Идентификатор = " ФормаЗапускаЗадания";
   НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыОткрытиеФормы();
   НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
   НоваяКоманда.Представление = "Выполнение в фоне";
   НоваяКоманда.Идентификатор = "ВыполнениеВФоне";
   НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода();
   Возврат ПараметрыРегистрации;
КонецФункции

Создаем одну команда для открытия формы, вторую для выполнения в фоне.

Не забудьте указать версию БСП. Если ее не указать, запуск процедуры модуля обработки с указанием структуры параметров работать не будет.

Процедура ЗадержкаПоВремени(Время)
   
   Дата = ТекущаяДата()+Время;
   Пока Дата > ТекущаяДата() Цикл
   КонецЦикла;	
	
КонецПроцедуры	

Процедура ВыполнитьКоманду(Идентификатор, Параметры) Экспорт
	
   Если Идентификатор = "ВыполнениеВФоне" Тогда

      ДатаНачала = Параметры.СтруктураДанных.ДатаНачала;
      ДатаОкончания = Параметры.СтруктураДанных.ДатаОкончания;

      Отказ = ПерепровестиДокументы(ДатаНачала, КоличествоДней);	
      Если Отказ тогда
         ЗадержкаПоВремени (1);
         Возврат;
      КонецЕсли; 
 	
      ДлительныеОперации.СообщитьПрогресс(100, "Документы успешно проведены!");
      ЗадержкаПоВремени (1);
		
   КонецЕсли;
	
КонецПроцедуры

Функция ПолучитьСписокДокументовНаСервере(ТекДата)
	
   Запрос = новый Запрос("ВЫБРАТЬ
	                      |	ТоварыОрганизаций.Регистратор КАК Документ,
	                      |	ТоварыОрганизаций.Период КАК Период
	                      |ИЗ
	                      |	РегистрНакопления.ТоварыОрганизаций КАК ТоварыОрганизаций
	                      |ГДЕ
	                      |	ТоварыОрганизаций.Период >= &ДатаНачала
	                      |	И ТоварыОрганизаций.Период <= &ДатаОкончания
	                      |
	                      |СГРУППИРОВАТЬ ПО
	                      |	ТоварыОрганизаций.Регистратор,
	                      |	ТоварыОрганизаций.Период
	                      |
	                      |УПОРЯДОЧИТЬ ПО
	                      |	Период");
	
   Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ТекДата));
   Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(ТекДата));

   Результат = Запрос.Выполнить();
   СписокДокументов = Результат.Выгрузить();
	      
   Возврат СписокДокументов;
	 
КонецФункции

Функция ПерепровестиДокументы(ТекДата, КоличествоДней)
	
   ДокументыДляПроведения = ПолучитьСписокДокументовНаСервере(ТекДата);
	
   ТекущийДокумент = 0;
   ВсегоДокументов = ДокументыДляПроведения.Количество();
	
   Для каждого СтрокаТЗ Из ДокументыДляПроведения Цикл
		
      ДокументОбъект = СтрокаТЗ.Документ.ПолучитьОбъект();
	
      Попытка
         ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
      Исключение
         ДлительныеОперации.СообщитьПрогресс(-1, "Ошибка проведения документа: " + ДокументСсылка);
         Возврат Истина;
      КонецПопытки;
		
      ТекущийДокумент = ТекущийДокумент + 1;
      Если НЕ (ТекущийДокумент % 5) Тогда
         ДлительныеОперации.СообщитьПрогресс(Формат(ТекущийДокумент/ВсегоДокументов*100, "ЧЦ=3; ЧДЦ="), "Выполняется проведение документа: " + ДокументСсылка);
      КонецЕсли;			
		
   КонецЦикла;	
   Возврат Ложь;
	
КонецФункции

Здесь все просто, выполняется выборка документов по регистру «ТоварыОрганизаций» за переданный в фоновое задание период и документы последовательно перепроводятся. После каждого 5 документа отправляются данные о состоянии выполнения основному сеансу. В случае ошибки отправляется информация о документе, в котором произошла ошибка. Задержка в одну секунду нужна для обработки информации об ошибки в основном сеансе.

 

Тексты процедур модуля формы

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)

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

Если обработка открыта из списка внешних отчетов и обработок базы, свойство «ДополнительнаяОбработкаСсылка» будет заполнено. Если открываем обработку как внешнюю, ищем сохраненный вариант в базе.
 

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

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

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

Создаем фоновое задание, передаем в него параметры по периоду. Сохраняем идентификатор задания в реквизите формы и создаем обработчик ожидания.

&НаКлиенте
Процедура ПроверитьПрогрессВыполнения()

   СтруктураДанныхВыполнения = ОбновитьПрогрессНаСервере(ИдентификаторЗадания);
	
   Если СтруктураДанныхВыполнения.Свойство("ПроцентВыполнения") Тогда 
      ПроцентВыполнения = СтруктураДанныхВыполнения.ПроцентВыполнения;
   КонецЕсли;
	
   Если СтруктураДанныхВыполнения.Свойство("СостояниеВыполнения") Тогда
      СостояниеВыполнения = СтруктураДанныхВыполнения.СостояниеВыполнения;
   КонецЕсли;	
	    	
   Если НЕ СтруктураДанныхВыполнения.ЗаданиеВыполнено Тогда
      ПодключитьОбработчикОжидания("ПроверитьПрогрессВыполнения", 0.3, Истина);
   КонецЕсли;	

КонецПроцедуры // ПроверитьПрогрессВыполнения()

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

   МассивСообщений = ДлительныеОперации.СообщенияПользователю(Ложь, ИдентификаторЗадания); 
   Если ТипЗнч(МассивСообщений) = Тип("ФиксированныйМассив") Тогда
		
      Для Каждого СтрокаСообщения Из МассивСообщений Цикл 
         Сообщить(СтрокаСообщения.Текст);
      КонецЦикла;
		
   КонецЕсли;
	
   РезультатВыгрузки = ДлительныеОперации.ПрочитатьПрогресс(ИдентификаторЗадания);
   Если ТипЗнч(РезультатВыгрузки) = Тип("Структура") Тогда
      
      Если РезультатВыгрузки.Свойство("Процент") Тогда
         
         Если РезультатВыгрузки.Процент = -1 Тогда
    	    ЗавершитьЗадание = Истина;
	 КонецЕсли;	 
         СтруктураДанныхВыполнения.Вставить("ПроцентВыполнения", РезультатВыгрузки.Процент);
			
      КонецЕсли;
		
      Если РезультатВыгрузки.Свойство("Текст") Тогда
        СтруктураДанныхВыполнения.Вставить("СостояниеВыполнения", РезультатВыгрузки.Текст);
      КонецЕсли;
		
   КонецЕсли;
	
   Если ДлительныеОперации.ЗаданиеВыполнено(ИдентификаторЗадания) Тогда
      СтруктураДанныхВыполнения.Вставить("ЗаданиеВыполнено", Истина);
   Иначе
      СтруктураДанныхВыполнения.Вставить("ЗаданиеВыполнено", Ложь);
   КонецЕсли;

   Если ЗавершитьЗадание Тогда
      ДлительныеОперации.ОтменитьВыполнениеЗадания(ИдентификаторЗадания);
      СтруктураДанныхВыполнения.Вставить("ЗаданиеВыполнено", Истина);
   КонецЕсли;
	
   Возврат СтруктураДанныхВыполнения;
	
КонецФункции

Проверяем состояние выполнения задания. Информацию о стадии выполнения задания отображаем виде прогресса, расположенного на форме и текстового поля. Также выводим сообщения, сформированные в фоновом задании. Например, если будет ошибка проведения документа, информация будет выведена в текстовом поле и в окне сообщений формы.

Если задание не выполнено, подключаем опять обработчик ожидания. Если в задании была ошибка при проведении документа «РезультатВыгрузки.Процент = -1», закрываем его принудительно. На всякий случай, так как оно и так должно закрыться через секунду.

&НаКлиенте
Процедура ПериодПриИзменении(Элемент)
	
   ДатаНачала = Период.ДатаНачала;
   ДатаОкончания = Период.ДатаОкончания;
	
КонецПроцедуры

&НаКлиенте
Процедура ДатаНачаллаПриИзменении(Элемент)
	
   Период.ДатаНачала = ДатаНачала;
	
КонецПроцедуры

&НаКлиенте
Процедура ДатаОкончанияПриИзменении(Элемент)
	
   Период.ДатаОкончания = ДатаОкончания;
	
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ЗавершитьВыполнениеНаСервере(ИдентификаторЗадания)
	
   ДлительныеОперации.ОтменитьВыполнениеЗадания(ИдентификаторЗадания);
	
КонецПроцедуры

&НаКлиенте
Процедура ЗавершитьВыполнение(Команда)
	
   ЗавершитьВыполнениеНаСервере(ИдентификаторЗадания);
   ПроцентВыполнения = 0;
   СостояниеВыполнения = "Выполнение прервано пользователем!"
	
КонецПроцедуры

Тут особо комментировать нечего. Настройка периода и принудительное завершение фонового задания.

Вот и все, получаем отслеживание работы фонового задания непосредственно в форме обработки.

Можно продолжить развивать данную тему, и реализовать многопоточность. Например перепроведение документов в потоках. Это может ускорить процесс в пять или более раз! Если эта тема интересна, напишите пожалуйста в комментариях.

До новых встреч!

129

См. также

Специальные предложения

Комментарии
Избранное Подписка Сортировка: Древо
1. mi1man 300 17.08.19 10:55 Сейчас в теме
Спасибо. Тема про "многопоточность" интересна .. буду ждать продолжения)
2. Xershi 695 17.08.19 17:05 Сейчас в теме
Действительно, плохо искали. Мало того имея БСП 2.3 с доработкой или 2.4 уже можно не рисовать на форме элементы а выводить прогресс отдельно и многопоточность тоже уже реализована! А перепроведение как уже говорилось не подойдет, потому что оно будет накладывать взаимоблокировки. Придется и их учесть при написании кода, что усложнит алгоритм на порядок.
4. ids79 3790 17.08.19 21:53 Сейчас в теме
(2)Многопоточность сама по себе ничего сверхестественного из себя не представляет. Интересен как раз сам механизм разбиения данных на блоки для выполнения в разных потоках. То-есть выделять группы документов, которые не будут мешать друг другу при проведении.
logarifm; +1 Ответить
3. aximo 817 17.08.19 18:48 Сейчас в теме
Дмитрий, ты молодец! интересные вещи пишешь!
5. ids79 3790 17.08.19 21:54 Сейчас в теме
6. logarifm 1046 18.08.19 10:24 Сейчас в теме
Можно добавить лепту? Если операции действительно длительные то для оптимизации можно бы подумать в сторону "шага процента" чтобы не долбить проверками каждые 0.3 секунд.
8. ids79 3790 19.08.19 10:05 Сейчас в теме
(6)В принципе, можно увеличить это время, так как все равно данные отправляются после каждого пятого документа.
7. borodatii 2 19.08.19 08:29 Сейчас в теме
Про многопоточное проведение вот тут достаточно подробно рассказано: https://infostart.ru/public/1102042
9. ids79 3790 19.08.19 10:06 Сейчас в теме
(7)Да, я тоже привел ссылку на эту статью в публицации
10. borodatii 2 19.08.19 12:58 Сейчас в теме
(9) оу, невнимательно прочитал, смотрел больше на код...
11. Darklight 19 19.08.19 13:18 Сейчас в теме
Мне кажется не очень хорошей идея - проверять прогресс каждые 0.3 сек через серверные вызовы. Ну тут можно долго спорить на тему оптимизации клиент-серверного взаимодействия, но от серверных вызовов тут никак не уйти (ну разве что всё взаимодействие не вынести во внешнюю Native-компоненту, которая будет подключена и в фоновом процессе и в клиенте, и будет производить обмен сообщениями через свой канал, например через TCP - но тут сложно искать универсальное решение), иначе всё сведётся лишь к увеличению периода опроса сервера
12. ids79 3790 19.08.19 15:19 Сейчас в теме
(11)Ну да, вариантов взаимодействия много, некоторые я привел в статье. Это не все, естественно.
Спасибо за дополнение
13. DonAlPatino 86 20.08.19 11:40 Сейчас в теме
Немножко не в тему, но коль про многопоточность заговорили ... а в oscript можно ее как-то организовать и результаты в главную программу вернуть? Что-то не нашел ничего на вскидку...
14. ids79 3790 20.08.19 13:03 Сейчас в теме
Оставьте свое сообщение