Введение
Недавно столкнулся с необходимостью увеличить скорость работы одной внешней обработки. Оказалось, что начиная с версии БСП 3.1.6 появился новый функционал.
Новые возможности для разработчиков в версии 3.1.6
Базовая функциональность
- Для ускорения выполнения длительных операций предусмотрена возможность выполнить обработчик длительной операции в несколько потоков. Для этого необходимо:
- Разделить данные на наборы, где каждый элемент набора будет обработан в отдельном фоновом задании.
- Из программного интерфейса общего модуля
ДлительныеОперации
для запуска процедуры длительной операции вызвать функцию ВыполнитьПроцедуруВНесколькоПотоков
(или ВыполнитьФункциюВНесколькоПотоков
), передав третьим параметром сформированный набор данных. Подробное описание параметров см. в комментарии к этим функциям.
- Максимально допустимое количество одновременно работающих фоновых заданий может быть задано администратором в разделе Администрирование - Общие настройки - Производительность
- В файловой информационной базе и при работе в модели сервиса длительные операции всегда выполняются в один поток.
- Пример см. в демонстрационной конфигурации в форме
ЗагрузкаАдресногоКлассификатора
регистра сведений АдресныеОбъекты.
В описании функции ВыполнитьПроцедуруВНесколькоПотоков (или ВыполнитьФункциюВНесколькоПотоков, но для удобства не буду разделять) описано, что код который будет выполняться в фоне должен быть в:
- В общем модуле ("МойОбщийМодуль.МояПроцедура")
- Модуле менеджера объекта ("Отчет.ЗагруженныеДанные.Сформировать")
- Модуле обработки ("Обработка.ЗагрузкаДанных.МодульОбъекта.Загрузить")
Но мне нужно было запустить процедуру модуля объекта внешней обработки, встроенной в справочник Дополнительные отчеты и обработки. В данном контексте я вспомнил про процедуру ВыполнитьПроцедуруМодуляОбъектаОбработки, что натолкнуло на мысль о том, что есть явно неописанная возможность многопоточно запустить код находящийся в модуле объекта дополнительной обработки, если в качестве первого параметра в функцию ВыполнитьПроцедуруВНесколькоПотоков передать Выполнить ПроцедуруМодуляОбъектаОбработки. Однако при первой попытке реализовать данный вариант я столкнулся с непониманием к ак правильно передать параметры, примеров такого использования функции не нашел, поэтому появилась идея разобраться и опубликовать результаты.
Цели
Перед тем как приступать к реализации, поставим цели, которых хотим достичь в рамках публикации.
- Публикация должна ответить читателю на ряд вопросов:
- Как многопоточно запустить процедуру расположенную в серверном общем модуле?
- Как с помощью ДлительныеОперации запустить процедуру модуля объекта внешней обработки?
- Как многопоточно запустить процедуру модуля объекта внешней обработки?
- Как отобразить прогресс многопоточной операции?
- В публикации должны быть продемонстрированы замеры производительности в зависимости от кол-ва потоков, наглядно показан выигрыш в производительности
Цели поставлены, приступим к реализации.
Реализация
Разработка проводилось на демонстрационной базе БСП версии 3.7.1.327.
Начнем с краткого описания того, что будет делать наша обработка:
- Получаем выборку ссылок на N-ое кол-во документов _ДемоПоступлениеТоваров
- Разбиваем ее на порции размером N/k, где k - кол-во потоков
- Формируем структуры параметров для функции ДлительныеОперации.ВыполнитьПроцедуруВНесколькоПотоков
- Запускаем длительную операцию которая поменяет в каждом обрабатываемом документе значение реквизита Комментарий
Форма обработки
Модуль объекта
Процедура ниже будет запускаться многопоточно
Процедура ПоменятьКомментарииМногопоточно(Параметры, АдресВоВременномХранилище) Экспорт
КолвоОбъектовКОбработке = Параметры.ТЗДляФоновогоЗадания.Количество();
//Рекомендуется не сообщать прогресс более 100 раз (см. ДлительныеОперации.СообщитьПрогресс)
//Пусть запущено N потоков, на каждый поток выделим 100/N сообщений прогресса
//В рамках конкретного потока будем сообщать прогресс с шагом
ШагСообщенияПрогресса = Окр(КолвоОбъектовКОбработке / (100 / Параметры.ВсегоПотоков));
КолвоОбработанныхОбъектовДляСообщенияПрогресса = ШагСообщенияПрогресса;
КолвоОбработанныхОбъектов = 0;
КоличествоОшибок = 0;
Для Каждого Стр Из Параметры.ТЗДляФоновогоЗадания Цикл
ДокументОбъект = Стр.Ссылка.ПолучитьОбъект();
ДокументОбъект.Организация = Параметры.Организация;
ДокументОбъект.Комментарий = 1;
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
Исключение
КоличествоОшибок = КоличествоОшибок + 1;
КонецПопытки;
КолвоОбработанныхОбъектов = КолвоОбработанныхОбъектов + 1;
Если КолвоОбработанныхОбъектов = КолвоОбработанныхОбъектовДляСообщенияПрогресса Тогда
ДлительныеОперации.СообщитьПрогресс(
Окр(КолвоОбработанныхОбъектов / КолвоОбъектовКОбработке),
Параметры.КодПотока);
КолвоОбработанныхОбъектовДляСообщенияПрогресса = КолвоОбработанныхОбъектовДляСообщенияПрогресса
+ ШагСообщенияПрогресса;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Модуль формы
Первым делом ответим на вопрос "Как в длительной операции запустить процедуру модуля объекта внешней обработки?":
Как с помощью ДлительныеОперации запустить процедуру модуля объекта внешней обработки?
&НаКлиенте
Процедура ПоменятьКомментарии(Команда)
Задание = ПоменятьКомментарииНаСервере(УникальныйИдентификатор);
НастройкиОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
НастройкиОжидания.ВыводитьОкноОжидания = Ложь;
НастройкиОжидания.ОповещениеОПрогрессеВыполнения = Новый ОписаниеОповещения("ПрогрессВыполнения", ЭтотОбъект);
НастройкиОжидания.ВыводитьПрогрессВыполнения = Истина;
ОповещениеОЗавершении = Новый ОписаниеОповещения(
"ПослеЗавершенияДлительнойОперации",
ЭтотОбъект);
ДлительныеОперацииКлиент.ОжидатьЗавершение(Задание, ОповещениеОЗавершении, НастройкиОжидания);
КонецПроцедуры
&НаСервере
Функция ПоменятьКомментарииНаСервере(УникальныйИдентификатор)
ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияПроцедуры();
ПараметрыВыполнения.НаименованиеФоновогоЗадания = НСтр("ru = 'Выполнение однопоточной функции'");
ПараметрыВыполнения.ЗапуститьВФоне = Истина;
ВремТЗ = ПолучитьТзДляМногопоточнойОперации();
//Подготовим адрес в хранилище
АдресВХранилище = ПоместитьВоВременноеХранилище(Неопределено);
//Параметры нашей процедуры в модуле объекта
ПараметрыПроцедурыМодуляОбъекта = Новый Структура;
ПараметрыПроцедурыМодуляОбъекта.Вставить("ТЗДляФоновогоЗадания", ВремТЗ);
ПараметрыПроцедурыМодуляОбъекта.Вставить("Организация", Организация);
//запуск фоновой процедуры происходит через метод "ВыполнитьПроцедуруМодуляОбъектаОбработки",
//куда мы передаем данные об обработке и о процедуре, которую необходимо запустить
ПараметрыЗадания = Новый Структура;
//имя внешней обработки
ПараметрыЗадания.Вставить("ИмяОбработки", "ВнешняяОбработка.ВнешняяОбработкаМногопоточно");
//имя экспортной серверной процедуры обработки
ПараметрыЗадания.Вставить("ИмяМетода", "ПоменятьКомментарииМногопоточно");
//входящие параметры процедуры
ПараметрыЗадания.Вставить("ПараметрыВыполнения", ПараметрыПроцедурыМодуляОбъекта);
//признак внешней обработки
ПараметрыЗадания.Вставить("ЭтоВнешняяОбработка", Истина);
//ссылка на доп. обработку в базе
ПараметрыЗадания.Вставить("ДополнительнаяОбработкаСсылка",
Справочники.ДополнительныеОтчетыИОбработки.НайтиПоНаименованию("Внешняя обработка многопоточно"));
ИмяФункцииИлиПроцедуры = "ДлительныеОперации.ВыполнитьПроцедуруМодуляОбъектаОбработки";
//Параметры, начиная с 3-его - это параметры вызываемой процедуры,
//в нашем случае "ДлительныеОперации.ВыполнитьПроцедуруМодуляОбъектаОбработки".
//Она принимает на вход два параметра - нашу структуру ПараметрыЗадания и АдресВХранилище
ДлительнаяОперация = ДлительныеОперации.ВыполнитьПроцедуру(
ПараметрыВыполнения,
ИмяФункцииИлиПроцедуры,
ПараметрыЗадания,
АдресВХранилище);
Если ДлительнаяОперация.Статус = "Ошибка" Тогда
Сообщить(СокрЛП(ДлительнаяОперация.Статус));
КонецЕсли;
Возврат ДлительнаяОперация;
КонецФункции
&НаСервереБезКонтекста
Функция ПолучитьТзДляМногопоточнойОперации()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1000
| _ДемоПоступлениеТоваров.Ссылка КАК Ссылка
|ИЗ
| Документ._ДемоПоступлениеТоваров КАК _ДемоПоступлениеТоваров";
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
И реализуем базовую функциональность для отображения прогресса
&НаКлиенте
Процедура ПрогрессВыполнения(Задание, ДополнительныеПараметры) Экспорт
Если Задание.Статус = "Выполняется" Тогда
Прогресс = ПрочитатьПрогресс(Задание.ИдентификаторЗадания);
Если Прогресс <> Неопределено Тогда
Индикатор = Прогресс.Процент;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПрочитатьПрогресс(ИдентификаторЗадания)
Возврат ДлительныеОперации.ПрочитатьПрогресс(ИдентификаторЗадания);
КонецФункции
&НаКлиенте
Процедура ПослеЗавершенияДлительнойОперации(Задание, ДополнительныеПараметры) Экспорт
Индикатор = 100;
СтрокаСостояния = ?(Задание = Неопределено, "Отменено", Задание.Статус);
КонецПроцедуры
Теперь ответим на вопрос "Как многопоточно запустить процедуру модуля объекта внешней обработки?" и "Как многопоточно запустить процедуру расположенную в серверном общем модуле?":
&НаКлиенте
Процедура ПоменятьКомментарииМногопоточно(Команда)
Задание = ПоменятьКомментарииМногопоточноНаСервере(УникальныйИдентификатор);
НастройкиОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
НастройкиОжидания.ВыводитьОкноОжидания = Ложь;
НастройкиОжидания.ОповещениеОПрогрессеВыполнения = Новый ОписаниеОповещения("ПрогрессВыполнения", ЭтотОбъект);
НастройкиОжидания.ВыводитьПрогрессВыполнения = Истина;
НастройкиОжидания.Интервал = 3;
ОповещениеОЗавершении = Новый ОписаниеОповещения(
"ПослеЗавершенияДлительнойОперации",
ЭтотОбъект);
ДлительныеОперацииКлиент.ОжидатьЗавершение(Задание, ОповещениеОЗавершении, НастройкиОжидания);
КонецПроцедуры
&НаСервере
Функция ПоменятьКомментарииМногопоточноНаСервере(УникальныйИдентификатор)
ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
ПараметрыВыполнения.НаименованиеФоновогоЗадания = НСтр("ru = 'Выполнение многопоточной функции'");
ПараметрыВыполнения.ЗапуститьВФоне = Истина;
ВремТЗ = ПолучитьТзДляМногопоточнойОперации();
ЧислоСтрокВТаблице = ВремТЗ.Количество();
ВсегоПотоков = КолвоПотоков;
РазмерПорции = Цел(ЧислоСтрокВТаблице/ВсегоПотоков);
НачальныйИндексПорции = 0;
КонечныйИндексПорции = РазмерПорции - 1;
МассивЗаданий = Новый Массив;
МассивАдресовВХранилище = Новый Массив;
ЭтоПоследнийПоток = Ложь;
ПараметрыМетода = Новый Соответствие;
Для НомерПотока = 1 По ВсегоПотоков Цикл
ТЗДляФоновогоЗадания = Новый ТаблицаЗначений;
ТЗДляФоновогоЗадания = ВремТЗ.СкопироватьКолонки();
//если поток последний, то он обрабатывает все оставшиеся строки исходной ТЗ
Если НомерПотока = ВсегоПотоков Тогда
ЭтоПоследнийПоток = Истина;
КонечныйИндексПорции = ЧислоСтрокВТаблице - 1;
КонецЕсли;
Для Инд = НачальныйИндексПорции По КонечныйИндексПорции Цикл
ЗаполнитьЗначенияСвойств(ТЗДляФоновогоЗадания.Добавить(), ВремТЗ.Получить(Инд));
КонецЦикла;
//Подготовим адрес в хранилище, и добавим в массив всех адресов
АдресВХранилище = ПоместитьВоВременноеХранилище(Неопределено);
МассивАдресовВХранилище.Добавить(АдресВХранилище);
ПараметрыПроцедурыМодуляОбъекта = Новый Структура;
ПараметрыПроцедурыМодуляОбъекта.Вставить("ТЗДляФоновогоЗадания", ТЗДляФоновогоЗадания);
ПараметрыПроцедурыМодуляОбъекта.Вставить("Организация", Организация);
ПараметрыПроцедурыМодуляОбъекта.Вставить("КодПотока", "Поток" + НомерПотока);
ПараметрыПроцедурыМодуляОбъекта.Вставить("ВсегоПотоков", ВсегоПотоков);
Если ПроцедураВМодулеОбъекта() Тогда
//запуск фоновой процедуры происходит через метод "ВыполнитьПроцедуруМодуляОбъектаОбработки",
//куда мы передаем данные об обработке и о процедуре, которую необходимо запустить
ПараметрыЗадания = Новый Структура;
//имя внешней обработки
ПараметрыЗадания.Вставить("ИмяОбработки", "ВнешняяОбработка.ВнешняяОбработкаМногопоточно");
//имя экспортной серверной процедуры обработки
ПараметрыЗадания.Вставить("ИмяМетода", "ПоменятьКомментарииМногопоточно");
//входящие параметры процедуры
ПараметрыЗадания.Вставить("ПараметрыВыполнения", ПараметрыПроцедурыМодуляОбъекта);
//признак внешней обработки
ПараметрыЗадания.Вставить("ЭтоВнешняяОбработка", Истина);
//ссылка на доп. обработку в базе
ПараметрыЗадания.Вставить("ДополнительнаяОбработкаСсылка",
Справочники.ДополнительныеОтчетыИОбработки.НайтиПоНаименованию("Многопоточность Тестовый Стенд"));
Иначе
ПараметрыЗадания = ПараметрыПроцедурыМодуляОбъекта;
КонецЕсли;
//Если раннее в процедуру ДлительныеОперации.ВыполнитьПроцедуру начиная с 3-его передавались
//параметры функции ДлительныеОперации.ВыполнитьПроцедуруМодуляОбъектаОбработки,
//то в данном случае мы должны в нужном порядке поместить их в массив
ПараметрыЗаданияМассив = Новый Массив;
ПараметрыЗаданияМассив.Добавить(ПараметрыЗадания);
ПараметрыЗаданияМассив.Добавить(АдресВХранилище);
//На выходе цикла получим соответсвие, с кол-вом элементов равным кол-ву потоков.
//Каждый поток запускает ДлительныеОперации.ВыполнитьПроцедуруМодуляОбъектаОбработки,
//параметры которой содержатся в массиве ПараметрыЗаданияМассив.
//А ВыполнитьПроцедуруМодуляОбъектаОбработки в свою очередь уже запускает нужную нам
//процедуру модуля обработки с параметрами ПараметрыПроцедурыМодуляОбъекта
ПараметрыМетода.Вставить("Поток" + НомерПотока, ПараметрыЗаданияМассив);
Если ЭтоПоследнийПоток Тогда Прервать; КонецЕсли;
НачальныйИндексПорции = НачальныйИндексПорции + РазмерПорции;
КонечныйИндексПорции = КонечныйИндексПорции + РазмерПорции;
КонецЦикла;
Если ПроцедураВМодулеОбъекта() Тогда
ИмяФункцииИлиПроцедуры = "ДлительныеОперации.ВыполнитьПроцедуруМодуляОбъектаОбработки";
Иначе
ИмяФункцииИлиПроцедуры = "МойОбщийМодуль.ПоменятьКомментарииМногопоточно";
КонецЕсли;
ДлительнаяОперация = ДлительныеОперации.ВыполнитьПроцедуруВНесколькоПотоков(
ИмяФункцииИлиПроцедуры,
ПараметрыВыполнения,
ПараметрыМетода);
Если ДлительнаяОперация.Статус = "Ошибка" Тогда
Сообщить(СокрЛП(ДлительнаяОперация.Статус));
КонецЕсли;
Возврат ДлительнаяОперация;
КонецФункции
Однако есть проблема - прогресс для многопоточной операции будет крайне неинформативным. Поясню, когда запускаем многопоточную длительную операцию, сначала запускается основной поток, который в свою очередь запускает нужное нам кол-во потоков
Как я понял основной поток передает только информацию о завершении дочерних потоков, а в силу того что данные разбиваются на равные порции, и потоки заканчиваются практически одновременно, прогресс бар в какой-то момент просто скаканет с 0 до 100. Необходимо каким-то образом синхронизировать прогрессы обработки в каждом потоке и выдать пользователю среднее значение. Типового функционала который в полной мере будет делать то, что описано я не нашел. Но в рамках многопоточного выполнения длительной операции есть несколько моментов, которые могут помочь реализовать собственный функционал:
1. В процедуру ПрогрессВыполнения попадает структура длительной операции (см. ДлительныеОперацииКлиент.ПараметрыОжидания) и что важно - в ключе ИдентификаторЗадания этой структуры, содержится именно идентификатор основной длительной операции.
2. Есть функция СерверныеОповещения.СерверныеОповещенияДляКлиента благодаря, которой можно получить все сообщения о прогрессе из потоков, по идентификатору основного потока . А имея эти оповещения остается правильно их обработать.
Синтезируем из этих фактов процедуру которая корректно отображать прогресс многопоточной операции, а если точнее подправим процедуры ПрогрессВыполнения и ПрочитатьПрогресс:
&НаКлиенте
Процедура ПрогрессВыполнения(Задание, ДополнительныеПараметры) Экспорт
Если Задание.Статус = "Выполняется" Тогда
Прогресс = ПрочитатьПрогресс(Задание.ИдентификаторЗадания, КолвоПотоков);
Если Прогресс > 99 Тогда
Индикатор = 99;
ИначеЕсли Прогресс < Индикатор Тогда
Возврат;
Иначе
Индикатор = Прогресс;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПрочитатьПрогресс(ИдентификаторЗадания, КолвоПотоков)
//В массиве элементы отсортированы по возрастанию даты оповещения, будем обходить с конца
МассивОповещений = СерверныеОповещения.СерверныеОповещенияДляКлиента(ИдентификаторЗадания);
ТекущийСуммарныйПрогрессПоПотокам = 0;
МассивПотоков = МассивКодовПотоков(КолвоПотоков);
Инд = МассивОповещений.ВГраница();
Пока Инд >= 0 Цикл
Если МассивПотоков.Количество() <= 0 Тогда
Прервать;
КонецЕсли;
Попытка
Прогресс = МассивОповещений[Инд].Содержимое.Результат.Результат.Прогресс;
Исключение
Инд = Инд - 1;
Продолжить;
КонецПопытки;
//Типовые сообщения о прогрессе содержат доп параметр
//Они не должны оказывать влияния, будем их пропускать
Если ТипЗнч(Прогресс) = Тип("Структура") И Прогресс.Свойство("ДополнительныеПараметры") Тогда
Если Прогресс.ДополнительныеПараметры = "ПрогрессМногопоточногоПроцесса" Тогда
Инд = Инд - 1;
Продолжить;
КонецЕсли;
КонецЕсли;
НайденныйПоток = МассивПотоков.Найти(Прогресс.Текст);
Если Не НайденныйПоток = Неопределено Тогда
ТекущийСуммарныйПрогрессПоПотокам = ТекущийСуммарныйПрогрессПоПотокам + Прогресс.Процент;
МассивПотоков.Удалить(НайденныйПоток);
КонецЕсли;
Инд = Инд - 1;
КонецЦикла;
Возврат Окр(ТекущийСуммарныйПрогрессПоПотокам / КолвоПотоков);
КонецФункции
&НаСервереБезКонтекста
Функция МассивКодовПотоков(КолвоПотоков)
МассивВозврата = Новый Массив;
Для НомерПотока = 1 По КолвоПотоков Цикл
МассивВозврата.Добавить("Поток" + НомерПотока);
КонецЦикла;
Возврат МассивВозврата;
КонецФункции
Полный код модуля формы
&НаКлиенте
Процедура ПрогрессВыполнения(Задание, ДополнительныеПараметры) Экспорт
Если Задание.Статус = "Выполняется" Тогда
Прогресс = ПрочитатьПрогресс(Задание.ИдентификаторЗадания, КолвоПотоков);
Если Прогресс > 99 Тогда
Индикатор = 99;
ИначеЕсли Прогресс < Индикатор Тогда
Возврат;
Иначе
Индикатор = Прогресс;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПрочитатьПрогресс(ИдентификаторЗадания, КолвоПотоков)
//В массиве элементы отсортированы по возрастанию даты оповещения, будем обходить с конца
МассивОповещений = СерверныеОповещения.СерверныеОповещенияДляКлиента(ИдентификаторЗадания);
ТекущийСуммарныйПрогрессПоПотокам = 0;
МассивПотоков = МассивКодовПотоков(КолвоПотоков);
Инд = МассивОповещений.ВГраница();
Пока Инд >= 0 Цикл
Если МассивПотоков.Количество() <= 0 Тогда
Прервать;
КонецЕсли;
Попытка
Прогресс = МассивОповещений[Инд].Содержимое.Результат.Результат.Прогресс;
Исключение
Инд = Инд - 1;
Продолжить;
КонецПопытки;
//Типовые сообщения о прогрессе содержат доп параметр
//Они не должны оказывать влияния, будем их пропускать
Если ТипЗнч(Прогресс) = Тип("Структура") И Прогресс.Свойство("ДополнительныеПараметры") Тогда
Если Прогресс.ДополнительныеПараметры = "ПрогрессМногопоточногоПроцесса" Тогда
Инд = Инд - 1;
Продолжить;
КонецЕсли;
КонецЕсли;
НайденныйПоток = МассивПотоков.Найти(Прогресс.Текст);
Если Не НайденныйПоток = Неопределено Тогда
ТекущийСуммарныйПрогрессПоПотокам = ТекущийСуммарныйПрогрессПоПотокам + Прогресс.Процент;
МассивПотоков.Удалить(НайденныйПоток);
КонецЕсли;
Инд = Инд - 1;
КонецЦикла;
Возврат Окр(ТекущийСуммарныйПрогрессПоПотокам / КолвоПотоков);
КонецФункции
&НаСервереБезКонтекста
Функция МассивКодовПотоков(КолвоПотоков)
МассивВозврата = Новый Массив;
Для НомерПотока = 1 По КолвоПотоков Цикл
МассивВозврата.Добавить("Поток" + НомерПотока);
КонецЦикла;
Возврат МассивВозврата;
КонецФункции
&НаКлиенте
Процедура ПоменятьКомментарииМногопоточно(Команда)
Задание = ПоменятьКомментарииМногопоточноНаСервере(УникальныйИдентификатор);
НастройкиОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
НастройкиОжидания.ВыводитьОкноОжидания = Ложь;
НастройкиОжидания.ОповещениеОПрогрессеВыполнения = Новый ОписаниеОповещения("ПрогрессВыполнения", ЭтотОбъект);
НастройкиОжидания.ВыводитьПрогрессВыполнения = Истина;
НастройкиОжидания.Интервал = 3;
ОповещениеОЗавершении = Новый ОписаниеОповещения(
"ПослеЗавершенияДлительнойОперации",
ЭтотОбъект);
ДлительныеОперацииКлиент.ОжидатьЗавершение(Задание, ОповещениеОЗавершении, НастройкиОжидания);
КонецПроцедуры
&НаСервере
Функция ПоменятьКомментарииМногопоточноНаСервере(УникальныйИдентификатор)
ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
ПараметрыВыполнения.НаименованиеФоновогоЗадания = НСтр("ru = 'Выполнение многопоточной функции'");
ПараметрыВыполнения.ЗапуститьВФоне = Истина;
ВремТЗ = ПолучитьТзДляМногопоточнойОперации();
ЧислоСтрокВТаблице = ВремТЗ.Количество();
ВсегоПотоков = КолвоПотоков;
РазмерПорции = Цел(ЧислоСтрокВТаблице/ВсегоПотоков);
НачальныйИндексПорции = 0;
КонечныйИндексПорции = РазмерПорции - 1;
МассивЗаданий = Новый Массив;
МассивАдресовВХранилище = Новый Массив;
ЭтоПоследнийПоток = Ложь;
ПараметрыМетода = Новый Соответствие;
Для НомерПотока = 1 По ВсегоПотоков Цикл
ТЗДляФоновогоЗадания = Новый ТаблицаЗначений;
ТЗДляФоновогоЗадания = ВремТЗ.СкопироватьКолонки();
//если поток последний, то он обрабатывает все оставшиеся строки исходной ТЗ
Если НомерПотока = ВсегоПотоков Тогда
ЭтоПоследнийПоток = Истина;
КонечныйИндексПорции = ЧислоСтрокВТаблице - 1;
КонецЕсли;
Для Инд = НачальныйИндексПорции По КонечныйИндексПорции Цикл
ЗаполнитьЗначенияСвойств(ТЗДляФоновогоЗадания.Добавить(), ВремТЗ.Получить(Инд));
КонецЦикла;
//Подготовим адрес в хранилище, и добавим в массив всех адресов
АдресВХранилище = ПоместитьВоВременноеХранилище(Неопределено);
МассивАдресовВХранилище.Добавить(АдресВХранилище);
ПараметрыПроцедурыМодуляОбъекта = Новый Структура;
ПараметрыПроцедурыМодуляОбъекта.Вставить("ТЗДляФоновогоЗадания", ТЗДляФоновогоЗадания);
ПараметрыПроцедурыМодуляОбъекта.Вставить("Организация", Организация);
ПараметрыПроцедурыМодуляОбъекта.Вставить("КодПотока", "Поток" + НомерПотока);
ПараметрыПроцедурыМодуляОбъекта.Вставить("ВсегоПотоков", ВсегоПотоков);
Если ПроцедураВМодулеОбъекта() Тогда
//запуск фоновой процедуры происходит через метод "ВыполнитьПроцедуруМодуляОбъектаОбработки",
//куда мы передаем данные об обработке и о процедуре, которую необходимо запустить
ПараметрыЗадания = Новый Структура;
//имя внешней обработки
ПараметрыЗадания.Вставить("ИмяОбработки", "ВнешняяОбработка.ВнешняяОбработкаМногопоточно");
//имя экспортной серверной процедуры обработки
ПараметрыЗадания.Вставить("ИмяМетода", "ПоменятьКомментарииМногопоточно");
//входящие параметры процедуры
ПараметрыЗадания.Вставить("ПараметрыВыполнения", ПараметрыПроцедурыМодуляОбъекта);
//признак внешней обработки
ПараметрыЗадания.Вставить("ЭтоВнешняяОбработка", Истина);
//ссылка на доп. обработку в базе
ПараметрыЗадания.Вставить("ДополнительнаяОбработкаСсылка",
Справочники.ДополнительныеОтчетыИОбработки.НайтиПоНаименованию("Многопоточность Тестовый Стенд"));
Иначе
ПараметрыЗадания = ПараметрыПроцедурыМодуляОбъекта;
КонецЕсли;
//Если раннее в процедуру ДлительныеОперации.ВыполнитьПроцедуру начиная с 3-его передавались
//параметры функции ДлительныеОперации.ВыполнитьПроцедуруМодуляОбъектаОбработки,
//то в данном случае мы должны в нужном порядке поместить их в массив
ПараметрыЗаданияМассив = Новый Массив;
ПараметрыЗаданияМассив.Добавить(ПараметрыЗадания);
ПараметрыЗаданияМассив.Добавить(АдресВХранилище);
//На выходе цикла получим соответсвие, с кол-вом элементов равным кол-ву потоков.
//Каждый поток запускает ДлительныеОперации.ВыполнитьПроцедуруМодуляОбъектаОбработки,
//параметры которой содержатся в массиве ПараметрыЗаданияМассив.
//А ВыполнитьПроцедуруМодуляОбъектаОбработки в свою очередь уже запускает нужную нам
//процедуру модуля обработки с параметрами ПараметрыПроцедурыМодуляОбъекта
ПараметрыМетода.Вставить("Поток" + НомерПотока, ПараметрыЗаданияМассив);
Если ЭтоПоследнийПоток Тогда Прервать; КонецЕсли;
НачальныйИндексПорции = НачальныйИндексПорции + РазмерПорции;
КонечныйИндексПорции = КонечныйИндексПорции + РазмерПорции;
КонецЦикла;
Если ПроцедураВМодулеОбъекта() Тогда
ИмяФункцииИлиПроцедуры = "ДлительныеОперации.ВыполнитьПроцедуруМодуляОбъектаОбработки";
Иначе
ИмяФункцииИлиПроцедуры = "МойОбщийМодуль.ПоменятьКомментарииМногопоточно";
КонецЕсли;
ДлительнаяОперация = ДлительныеОперации.ВыполнитьПроцедуруВНесколькоПотоков(
ИмяФункцииИлиПроцедуры,
ПараметрыВыполнения,
ПараметрыМетода);
Если ДлительнаяОперация.Статус = "Ошибка" Тогда
Сообщить(СокрЛП(ДлительнаяОперация.Статус));
КонецЕсли;
Возврат ДлительнаяОперация;
КонецФункции
Замеры производительности
Были произведены замеры производительности в зависимости от кол-ва потоков. Результаты для разного кол-ва обрабатываемых документов на графиках
Из графиков видно, что вне зависимости от кол-ва обрабатываемых документов, использование даже двух потоков на данном конкретном примере дает выигрыш в производительности примерно в 2 раза.
Выводы
В публикации:
- Приведен шаблон для запуска многопоточной операции для различных вариантов размещения многопоточной операции, в том числе в модуле объекта внешней обработки, встроенной в справочник Дополнительные отчеты и обработки;
- Представлен вариант корректного отображения прогресса многопоточной операции;
- Приведены замеры, демонстрирующие целесообразность использования нового механизма для увеличения производительности.
P.S.
Многопоточный режим выполнения процедуры с помощью методов БСП - примеры разработки
Гарантированно рабочий пример использования длительных операций на БСП с отображением прогресса. [Часть 1]
Многопоточность как способ ускорения некоторых процедур
Как ускорить 1С – Многопоточная обработка данных
- Многопоточность работает только в клиент-серверном варианте работы
- Обратите внимание на константу Кол ичествоПотоковДлительныхОпераций, по умолчанию значение 0 и больше 3 потоков запущено не будет. (см. ДлительныеОперации.ДопустимоеКоличествоПотоков)
- Весь код по теме представлен в публикации, в приложенной обработке реализован тестовый стенд для этой статьи с замерами времени, построением по ним графика и тп