Попадали ли вы в ситуацию, когда необходимо за короткий срок неким образом обработать большой набор данных? Перепровести документы, пересчитать данные регистров, заполнить вспомогательные справочники - если в наборе нет сложных зависимостей (расчет проведения одного документа не зависит от результата расчета другого) - то его можно разбить на несколько порций, и обрабатывать их в фоне параллельно. Загвоздка состоит в том, что фоновое задание возможно запустить только через экспортный метод неглобального общего модуля. Перепиливать конфу каждый раз когда возникает такая потребность - крайне неудобно. Отсюда возникает задача организовать выполнение произвольного кода в произвольном количестве фоновых заданий.
Пусть исходно есть некий источник данных для их последующей обработки - РезультатЗапроса, или ТаблицаЗначений. Т.е. собрали запросом необходимые к проведению документы, или - поместили их в таблицу значений (в том случае если алгоритм выборки слишком сложный для того, чтобы обойтись только запросом). Для демонстрации возьму с потолка такую задачу: необходимо отобрать реализацию, и для клиента с ФИО = "Иванов Иван Иванович" все документы провести. Составим запрос:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РеализацияТоваровИУслуг.Ссылка,
| РеализацияТоваровИУслуг.Клиент.ФИО КАК ФИОКлиента
|ИЗ
| Документ.РеализацияТоваровИУслуг КАК РеализацияТоваровИУслуг
|";
РезультатЗапроса = Запрос.Выполнить();
... и реализуем код для обработки данных:
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
об = Выборка.Ссылка.ПолучитьОбъект();
Если Выборка.ФИОКлиента = "Иванов Иван Иванович" Тогда
об.Записать(РежимЗаписиДокумента.Проведение);
КонецЕсли;
КонецЦикла;
Это не очень оптимальное решение, исключительно для демонстрации условие осталось внутри цикла - хотя конечно логичнее было бы сразу отобрать в запросе. Теперь запускаем этот код на выполнение в фоне. Распределим нагрузку: предположим что в нашей выборке 100`000 документов. Тогда распределяя это количество на 10 фоновых заданий получаем 10`000 документов на одно задание. Распределение выполняется функцией "ТаблицаФоновыхЗадач":
ТаблицаФоновыхЗадач = ФоновыеЗаданияСервер.ТаблицаФоновыхЗадач(РезультатЗапроса, 10, "Проведение документов");
После того, как таблица фоновых задач сформирована - необходимо подготовить код к передаче, параметром. Цикл по набору данных будет выполнен автоматически, поэтому понадобится только тело цикла. Этот кусок будет передан в метод ГК "Выполнить", его требуется преобразовать в строку. После того, как параметры подготовлены, их остается передать в функцию "ВыполнитьВФоне". В итоге текущий пример примет такой вид:
Код = "
| об = Выборка.Ссылка.ПолучитьОбъект();
| Если Выборка.ФИОКлиента = ""Иванов Иван Иванович"" Тогда
| об.Записать(РежимЗаписиДокумента.Проведение);
| КонецЕсли;";
ТаблицаФоновыхЗадач = ФоновыеЗаданияСервер.ТаблицаФоновыхЗадач(РезультатЗапроса, 10, "Проведение документов");
ФоновыеЗаданияСервер.ВыполнитьВФоне(ТаблицаФоновыхЗадач, Код);
Мониторить результат можно с помощью консоли инструментов разработчика (скриншот). Обратите внимание на колонку "Сообщения": при выполнении очередной итерации цикла по порции набора выполняется пустое "Сообщение пользователю". По количеству этих сообщений можно судить о количестве прошедших итераций, получаем своеобразный прогресс-бар: на скриншоте выделена строка фонового задания №5, и можно увидеть что из 10000 операции выполнено 4160.
Код, расположенный ниже помещается в неглобальный общий модуль. Удобно для этого использовать "ФоновыеЗаданияСервер".
////////////////////////////////////////////////////////////////////////////////
// Выполнение произвольного кода в фоне
// Служебная функция, для вызова фоновой обработки порции набора данных. См. "ВыполнитьВФоне"
//
//Параметры:
// Код - Строка, исполняемый код внутреннего языка
// ТаблицаНабораДанных - ТаблицаЗначений, см. "ТаблицаФоновыхЗадач", значение колонки "НаборДанных"
// ЦиклПоНаборуДанных - Булево, если истина - код выполняется внутри цикла по набору данных
// ПрерыватьПоИсключению - Булево, имеет смысл только при ЦиклПоНаборуДанных = Истина. Если Истина, и при
// исполнении кода возникла исключительная ситуация - прерывает обработку.
//
Процедура ВыполнитьКодПотокаПоНаборуДанных(Код, ТаблицаНабораДанных, ЦиклПоНаборуДанных, ПрерыватьПоИсключению) ЭКСПОРТ
Если ЦиклПоНаборуДанных Тогда
МассивЗарегистрированныхОшибок = Новый Массив;
Для каждого Выборка Из ТаблицаНабораДанных Цикл
Попытка
Выполнить(Код);
Исключение
ТекстОшибки = ОписаниеОшибки();
Если МассивЗарегистрированныхОшибок.Найти(ТекстОшибки) = Неопределено Тогда
ЗаписьЖурналаРегистрации("ФоновоеВыполнениеКода"
, УровеньЖурналаРегистрации.Ошибка
,
,
, ТекстОшибки);
МассивЗарегистрированныхОшибок.Добавить(ТекстОшибки);
КонецЕсли;
Если ПрерыватьПоИсключению Тогда
Прервать;
КонецЕсли;
КонецПопытки;
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "";
Сообщение.Сообщить();
КонецЦикла;
Иначе
Попытка
Выполнить(Код);
Исключение
ЗаписьЖурналаРегистрации("ФоновоеВыполнениеКода"
, УровеньЖурналаРегистрации.Ошибка
,
,
, ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
ТаблицаНабораДанных = Неопределено;
КонецПроцедуры
// Формирование таблицы порций фоновых задач, распределение строк источника пропорционально количеству фоновых заданий
//
//Параметры:
// Источник - РезультатЗапроса, ТаблицаЗначений
// КоличествоПотоков - Число
// Представление - Строка, описание задачи фонового задания
//
//Возвращаемое значение:
// ТаблицаЗначений
// *КлючПотока - УникальныйИдентификатор, ключ фонового задания
// *ПредставлениеПотока - Строка
// *НаборДанных - ТаблицаЗначений, по структуре аналогична источнику - порция данных текущего потока
//
Функция ТаблицаФоновыхЗадач(Источник, КоличествоПотоков, Представление = "") ЭКСПОРТ
// Формирование таблицы распределения по количеству фоновых задач
ТаблицаФоновыхЗадач = Новый ТаблицаЗначений;
ТаблицаФоновыхЗадач.Колонки.Добавить("КлючПотока");
ТаблицаФоновыхЗадач.Колонки.Добавить("ПредставлениеПотока");
ТаблицаФоновыхЗадач.Колонки.Добавить("НаборДанных");
ТаблицаПорций = Новый ТаблицаЗначений;
Для каждого Колонка Из Источник.Колонки Цикл
ТаблицаПорций.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения);
КонецЦикла;
Для Счетчик = 1 По КоличествоПотоков Цикл
СтрокаТаблицы = ТаблицаФоновыхЗадач.Добавить();
СтрокаТаблицы.КлючПотока = Новый УникальныйИдентификатор;
СтрокаТаблицы.НаборДанных = ТаблицаПорций.Скопировать();
КонецЦикла;
// Распределение записей выборки по порциям:
ТекущийПоток = 0;
Если ТипЗнч(Источник) = Тип("ТаблицаЗначений") Тогда
Для каждого Выборка Из Источник Цикл
СтрокаТаблицы = ТаблицаФоновыхЗадач[ТекущийПоток];
НовыйНаборДанных = СтрокаТаблицы.НаборДанных.Добавить();
ЗаполнитьЗначенияСвойств(НовыйНаборДанных, Выборка);
ТекущийПоток = ТекущийПоток + 1;
Если ТекущийПоток = КоличествоПотоков Тогда
ТекущийПоток = 0;
КонецЕсли;
КонецЦикла;
Иначе
Выборка = Источник.Выбрать();
Пока Выборка.Следующий() Цикл
СтрокаТаблицы = ТаблицаФоновыхЗадач[ТекущийПоток];
НовыйНаборДанных = СтрокаТаблицы.НаборДанных.Добавить();
ЗаполнитьЗначенияСвойств(НовыйНаборДанных, Выборка);
ТекущийПоток = ТекущийПоток + 1;
Если ТекущийПоток = КоличествоПотоков Тогда
ТекущийПоток = 0;
КонецЕсли;
КонецЦикла;
КонецЕсли;
МассивУдаляемыхСтрок = Новый Массив();
Счетчик = 0;
Для каждого СтрокаТаблицы Из ТаблицаФоновыхЗадач Цикл
КоличествоЗаписейНабора = СтрокаТаблицы.НаборДанных.Количество();
Если КоличествоЗаписейНабора = 0 Тогда
МассивУдаляемыхСтрок.Добавить(СтрокаТаблицы);
Продолжить;
КонецЕсли;
СтрЗаписей = "("+ КоличествоЗаписейНабора +" записей)";
Счетчик = Счетчик + 1;
НомерПотока = Формат(Счетчик, "ЧЦ=2; ЧВН=");
Если ПустаяСтрока(Представление) Тогда
СтрокаТаблицы.ПредставлениеПотока = "ФЗ №" + НомерПотока + ", ключ " + СтрокаТаблицы.КлючЗадачи + " " + СтрЗаписей;
Иначе
СтрокаТаблицы.ПредставлениеПотока = Представление + ", поток №" + НомерПотока + " " + СтрЗаписей + ".";
КонецЕсли;
КонецЦикла;
Для каждого СтрокаТаблицы Из МассивУдаляемыхСтрок Цикл
ТаблицаФоновыхЗадач.Удалить(СтрокаТаблицы);
КонецЦикла;
Возврат ТаблицаФоновыхЗадач;
КонецФункции
// Выполнение произвольного кода в произвольном количестве фоновых заданий
//
//Параметры:
// ТаблицаФоновыхЗадач - ТаблицаЗначений, см. "ТаблицаФоновыхЗадач"
// Код - Строка, исполняемый код внутреннего языка
// ОжидатьЗавершения - Булево
// ЦиклПоНаборуДанных - Булево, см. "ВыполнитьКодПотокаПоНаборуДанных"
//
//Возвращаемое значение:
// Массив - созданные фоновые задания
//
Функция ВыполнитьВФоне(ТаблицаФоновыхЗадач, Код, ОжидатьЗавершения = Ложь, ЦиклПоНаборуДанных = Истина, ПрерыватьПоИсключению = Истина) ЭКСПОРТ
ИмяМетода = "ФоновыеЗаданияСервер.ВыполнитьКодПотокаПоНаборуДанных";
МассивФоновыхЗаданий = Новый Массив();
Для каждого СтрокаТаблицы Из ТаблицаФоновыхЗадач Цикл
МассивПараметров = Новый Массив;
МассивПараметров.Добавить(Код);
МассивПараметров.Добавить(СтрокаТаблицы.НаборДанных);
МассивПараметров.Добавить(ЦиклПоНаборуДанных);
МассивПараметров.Добавить(ПрерыватьПоИсключению);
ФЗ = ФоновыеЗадания.Выполнить(
ИмяМетода
, МассивПараметров
, СтрокаТаблицы.КлючПотока
, СтрокаТаблицы.ПредставлениеПотока);
МассивФоновыхЗаданий.Добавить(ФЗ);
КонецЦикла;
Если ОжидатьЗавершения Тогда
ФоновыеЗадания.ОжидатьЗавершения(МассивФоновыхЗаданий);
КонецЕсли;
Возврат МассивФоновыхЗаданий;
КонецФункции