Тема достаточно широко освещена, но мне не попадалась информация о том, как отслеживать выполнение фонового задания собственным прогрессом, расположенным на форме.
Ниже несколько ссылок по фоновому выполнению кода:
- Фоновое задание – это просто - пример запуска фонового задания из общего модуля. Самый простой вариант, без отслеживания выполнения задания.
- Отображение прогресса выполнения длительных операций в БСП и их отладка в текущем сеансе – пример с использованием БСП. Запуск фонового задания, расположенного в общем модуле. Реализация собственной формы для отслеживания его выполнения.
- Запуск фонового задания из внешней обработки - более сложный пример с использованием БСП. Запуск фонового задания из внешней обработки и отслеживание выполнения задания средствами БСП.
- Произвольный код в фоновом режиме - также запуск фонового задания из внешней обработке. Частичное использование БСП. Правда метод «ЗапуститьВыполнениеВФоне» является устаревшим на данный момент.
Первый вариант сразу отпадает, так как не хочется вносить изменения в саму конфигурацию и необходимо отслеживать выполнение задания.
Второй и третий варианты хорошие, в них используется функционал БСП для отслеживания хода выполнения задания:
НастройкиОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтаФорма);
НастройкиОжидания.ВыводитьПрогрессВыполнения = Истина;
НастройкиОжидания.ВыводитьСообщения = Истина;
НастройкиОжидания.ТекстСообщения = НСтр("ru = 'Выполняется обработка данных.'");
ДлительныеОперацииКлиент.ОжидатьЗавершение(ДлительнаяОперация, Неопределено, НастройкиОжидания);
Этот функционал предоставляет широкие возможности для отслеживания хода выполнения в фоне. Но хочется создать в форме обработки свой собственный прогресс выполнения и управлять им. Ничего здесь сложного нет, но примеров такой реализации я не нашел. Может быть конечно плохо искал. По сему, выкладываю свой.
Возьмем понемногу из всех вариантов, описанных выше, и получим следующее решение:
Запуск фонового задания из модуля внешней обработки, подключенной к конфигурации с помощью функционала «Дополнительные отчеты и обработки». Причем обработку можно открывать как внешнюю. При этом код длительной операции будет выполняться из варианта, сохраненного в базе.
Для запуска фонового задания используем метод из БСП ДлительныеОперации.ВыполнитьВФоне.
Для отслеживания хода выполнения создадим на форме обработки два элемента управления: Прогресс и Тестовое поле.
Также будем выводить сообщения пользователю, которые были сформированы в фоновом задании, это очень удобно.
Дальше возникает вопрос, как получать информацию из фонового задания?
Это можно сделать несколькими способами:
- Через механизм сообщений – самый простой и распространенный способ, который используют методы из БСП.
- Через сервер взаимодействия – вариант не плохой, но сложный. Нужно разворачивать сам сервер взаимодействия. Понятно, что только для получения информации из фоновых заданий, это делать смысла нет.
- Через временное хранилище – в некоторых источниках описывается данный вариант взаимодействия, но он работает только для файловых баз. В клиент-серверном режиме работы, получить данные из временного хранилища можно только после завершения фонового задания. Это нам не подходит.
- Через хранилище общих настроек – я не тестировал данный вариант, но в статье про менеджер потоков судя по всему используется именно этот вариант.
Будем пользоваться первым способом используя методы БСП:
- ДлительныеОперации.СообщитьПрогресс
- ДлительныеОперации.ПрочитатьПрогресс
- ДлительныеОперации.СообщенияПользователю
Ниже привожу тексты модуля обработки и модуля формы:
Тексты процедур модуля обработки
Функция СведенияОВнешнейОбработке() Экспорт
ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1");
ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиДополнительнаяОбработка();
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
НоваяКоманда.Представление = "Форма запуска задания";
НоваяКоманда.Идентификатор = " ФормаЗапускаЗадания";
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыОткрытиеФормы();
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
НоваяКоманда.Представление = "Выполнение в фоне";
НоваяКоманда.Идентификатор = "ВыполнениеВФоне";
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода();
Возврат ПараметрыРегистрации;
КонецФункции
Создаем одну команда для открытия формы, вторую для выполнения в фоне.
Не забудьте указать версию БСП. Если ее не указать, запуск процедуры модуля обработки с указанием структуры параметров работать не будет.
Процедура ЗадержкаПоВремени(Время)
Дата = ТекущаяДата()+Время;
Пока Дата > ТекущаяДата() Цикл
КонецЦикла;
КонецПроцедуры
Процедура ВыполнитьКоманду(Идентификатор, Параметры) Экспорт
Если Идентификатор = "ВыполнениеВФоне" Тогда
ДатаНачала = Параметры.СтруктураДанных.ДатаНачала;
ДатаОкончания = Параметры.СтруктураДанных.ДатаОкончания;
Отказ = ПерепровестиДокументы(ДатаНачала, КоличествоДней);
Если Отказ тогда
ЗадержкаПоВремени (1);
Возврат;
КонецЕсли;
ДлительныеОперации.СообщитьПрогресс(100, "Документы успешно проведены!");
ЗадержкаПоВремени (1);
КонецЕсли;
КонецПроцедуры
Функция ПолучитьСписокДокументовНаСервере(ТекДата)
Запрос = новый Запрос("ВЫБРАТЬ
| ТоварыОрганизаций.Регистратор КАК Документ,
| ТоварыОрганизаций.Период КАК Период
|ИЗ
| РегистрНакопления.ТоварыОрганизаций КАК ТоварыОрганизаций
|ГДЕ
| ТоварыОрганизаций.Период >= &ДатаНачала
| И ТоварыОрганизаций.Период <= &ДатаОкончания
|
|СГРУППИРОВАТЬ ПО
| ТоварыОрганизаций.Регистратор,
| ТоварыОрганизаций.Период
|
|УПОРЯДОЧИТЬ ПО
| Период");
Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ТекДата));
Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(ТекДата));
Результат = Запрос.Выполнить();
СписокДокументов = Результат.Выгрузить();
Возврат СписокДокументов;
КонецФункции
Функция ПерепровестиДокументы(ТекДата, КоличествоДней)
ДокументыДляПроведения = ПолучитьСписокДокументовНаСервере(ТекДата);
ТекущийДокумент = 0;
ВсегоДокументов = ДокументыДляПроведения.Количество();
Для каждого СтрокаТЗ Из ДокументыДляПроведения Цикл
ДокументОбъект = СтрокаТЗ.Документ.ПолучитьОбъект();
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
ДлительныеОперации.СообщитьПрогресс(-1, "Ошибка проведения документа: " + ДокументСсылка);
Возврат Истина;
КонецПопытки;
ТекущийДокумент = ТекущийДокумент + 1;
Если НЕ (ТекущийДокумент % 5) Тогда
ДлительныеОперации.СообщитьПрогресс(Формат(ТекущийДокумент/ВсегоДокументов*100, "ЧЦ=3; ЧДЦ="), "Выполняется проведение документа: " + ДокументСсылка);
КонецЕсли;
КонецЦикла;
Возврат Ложь;
КонецФункции
Здесь все просто, выполняется выборка документов по регистру «ТоварыОрганизаций» за переданный в фоновое задание период и документы последовательно перепроводятся. После каждого 5 документа отправляются данные о состоянии выполнения основному сеансу. В случае ошибки отправляется информация о документе, в котором произошла ошибка. Задержка в одну секунду нужна для обработки информации об ошибки в основном сеансе.
Тексты процедур модуля формы
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
ВнешняяОбработка = РеквизитФормыВЗначение("Объект");
Параметры.Свойство("ДополнительнаяОбработкаСсылка", ДополнительнаяОбработкаСсылка);
Если ДополнительнаяОбработкаСсылка = Неопределено Тогда
ДополнительнаяОбработкаСсылка = Справочники.ДополнительныеОтчетыИОбработки.НайтиПоНаименованию(ВнешняяОбработка.Метаданные().Синоним);
КонецЕсли;
КонецПроцедуры
Если обработка открыта из списка внешних отчетов и обработок базы, свойство «ДополнительнаяОбработкаСсылка» будет заполнено. Если открываем обработку как внешнюю, ищем сохраненный вариант в базе.
&НаСервере
Функция НачатьВыполнениеСервернойКомандыВФоне(ВыполняемаяКоманда, УникальныйИдентификатор)
ПараметрыПроцедуры = Новый Структура("ДополнительнаяОбработкаСсылка, ИдентификаторКоманды, СтруктураДанных");
ПараметрыПроцедуры.ДополнительнаяОбработкаСсылка = ВыполняемаяКоманда.Ссылка;
ПараметрыПроцедуры.ИдентификаторКоманды = ВыполняемаяКоманда.Идентификатор;
ПараметрыПроцедуры.СтруктураДанных = ВыполняемаяКоманда.СтруктураДанных;
НастройкиЗапуска = ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
НастройкиЗапуска.НаименованиеФоновогоЗадания = НСтр("ru = 'Выполнение перепроведения документов'");
Возврат ДлительныеОперации.ВыполнитьВФоне("ДополнительныеОтчетыИОбработки.ВыполнитьКоманду", ПараметрыПроцедуры, НастройкиЗапуска);
КонецФункции
&НаКлиенте
Процедура ПерепровестиДокументы(Команда)
СтруктураДанных = новый Структура;
СтруктураДанных.Вставить("ДатаНачала", ДатаНачала);
СтруктураДанных.Вставить("ДатаОкончания", ДатаОкончания);
// выполнить команду в фоновом режиме
ВыполняемаяКоманда = Новый Структура("Ссылка, Идентификатор, СтруктураДанных", ДополнительнаяОбработкаСсылка, "ВыполнениеВФоне", СтруктураДанных);
ДлительнаяОперация = НачатьВыполнениеСервернойКомандыВФоне(ВыполняемаяКоманда, Новый УникальныйИдентификатор);
ИдентификаторЗадания = ДлительнаяОперация.ИдентификаторЗадания;
ПодключитьОбработчикОжидания("ПроверитьПрогрессВыполнения", 0.3, Истина);
КонецПроцедуры
Создаем фоновое задание, передаем в него параметры по периоду. Сохраняем идентификатор задания в реквизите формы и создаем обработчик ожидания.
&НаКлиенте
Процедура ПроверитьПрогрессВыполнения()
СтруктураДанныхВыполнения = ОбновитьПрогрессНаСервере(ИдентификаторЗадания);
Если СтруктураДанныхВыполнения.Свойство("ПроцентВыполнения") Тогда
ПроцентВыполнения = СтруктураДанныхВыполнения.ПроцентВыполнения;
КонецЕсли;
Если СтруктураДанныхВыполнения.Свойство("СостояниеВыполнения") Тогда
СостояниеВыполнения = СтруктураДанныхВыполнения.СостояниеВыполнения;
КонецЕсли;
Если НЕ СтруктураДанныхВыполнения.ЗаданиеВыполнено Тогда
ПодключитьОбработчикОжидания("ПроверитьПрогрессВыполнения", 0.3, Истина);
КонецЕсли;
КонецПроцедуры // ПроверитьПрогрессВыполнения()
&НаСервереБезКонтекста
Функция ОбновитьПрогрессНаСервере(ИдентификаторЗадания)
СтруктураДанныхВыполнения = новый Структура;
ЗавершитьЗадание = Ложь;
МассивСообщений = ДлительныеОперации.СообщенияПользователю(Ложь, ИдентификаторЗадания);
Если ТипЗнч(МассивСообщений) = Тип("ФиксированныйМассив") Тогда
Для Каждого СтрокаСообщения Из МассивСообщений Цикл
Сообщить(СтрокаСообщения.Текст);
КонецЦикла;
КонецЕсли;
РезультатВыгрузки = ДлительныеОперации.ПрочитатьПрогресс(ИдентификаторЗадания);
Если ТипЗнч(РезультатВыгрузки) = Тип("Структура") Тогда
Если РезультатВыгрузки.Свойство("Процент") Тогда
Если РезультатВыгрузки.Процент = -1 Тогда
ЗавершитьЗадание = Истина;
КонецЕсли;
СтруктураДанныхВыполнения.Вставить("ПроцентВыполнения", РезультатВыгрузки.Процент);
КонецЕсли;
Если РезультатВыгрузки.Свойство("Текст") Тогда
СтруктураДанныхВыполнения.Вставить("СостояниеВыполнения", РезультатВыгрузки.Текст);
КонецЕсли;
КонецЕсли;
Если ДлительныеОперации.ЗаданиеВыполнено(ИдентификаторЗадания) Тогда
СтруктураДанныхВыполнения.Вставить("ЗаданиеВыполнено", Истина);
Иначе
СтруктураДанныхВыполнения.Вставить("ЗаданиеВыполнено", Ложь);
КонецЕсли;
Если ЗавершитьЗадание Тогда
ДлительныеОперации.ОтменитьВыполнениеЗадания(ИдентификаторЗадания);
СтруктураДанныхВыполнения.Вставить("ЗаданиеВыполнено", Истина);
КонецЕсли;
Возврат СтруктураДанныхВыполнения;
КонецФункции
Проверяем состояние выполнения задания. Информацию о стадии выполнения задания отображаем виде прогресса, расположенного на форме и текстового поля. Также выводим сообщения, сформированные в фоновом задании. Например, если будет ошибка проведения документа, информация будет выведена в текстовом поле и в окне сообщений формы.
Если задание не выполнено, подключаем опять обработчик ожидания. Если в задании была ошибка при проведении документа «РезультатВыгрузки.Процент = -1», закрываем его принудительно. На всякий случай, так как оно и так должно закрыться через секунду.
&НаКлиенте
Процедура ПериодПриИзменении(Элемент)
ДатаНачала = Период.ДатаНачала;
ДатаОкончания = Период.ДатаОкончания;
КонецПроцедуры
&НаКлиенте
Процедура ДатаНачаллаПриИзменении(Элемент)
Период.ДатаНачала = ДатаНачала;
КонецПроцедуры
&НаКлиенте
Процедура ДатаОкончанияПриИзменении(Элемент)
Период.ДатаОкончания = ДатаОкончания;
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ЗавершитьВыполнениеНаСервере(ИдентификаторЗадания)
ДлительныеОперации.ОтменитьВыполнениеЗадания(ИдентификаторЗадания);
КонецПроцедуры
&НаКлиенте
Процедура ЗавершитьВыполнение(Команда)
ЗавершитьВыполнениеНаСервере(ИдентификаторЗадания);
ПроцентВыполнения = 0;
СостояниеВыполнения = "Выполнение прервано пользователем!"
КонецПроцедуры
Тут особо комментировать нечего. Настройка периода и принудительное завершение фонового задания.
Вот и все, получаем отслеживание работы фонового задания непосредственно в форме обработки.
Можно продолжить развивать данную тему, и реализовать многопоточность. Например перепроведение документов в потоках. Это может ускорить процесс в пять или более раз! Если эта тема интересна, напишите пожалуйста в комментариях.
До новых встреч!