В своей статье о планах обмена я поделился результатами исследования этого объекта конфигурации.
В этой публикации я хочу озвучить идею как можно распараллелить выгрузку сообщения обмена.
Заранее прошу прощения если это "баян". В таком случае просьба дать ссылки где почитать источники.
Код состоит из двух частей:
1. Управление формированием фоновых заданий и объединение результатов их работы.
2. Управление выполнением списка фоновых заданий.
Полный исходный код реализации идеи находится во вложенном к публикации файле.
Идея заключается в следующем:
1. Мы как обычно начинаем выгрузку.
2. В момент, когда начинаем выбирать изменения, мы распараллеливаем запись файла сообщения обмена по объектам конфигурации, которые входят в состав плана обмена. Для каждого такого объекта мы создаём отдельное фоновое задание и пишем все его изменения в отдельный файл - часть сообщения обмена. Подробнее можно посмотреть в моей статье в разделе "Запись изменений в сообщение обмена".
3. Ожидаем завершения выполнения всех сформированных заранее фоновых заданий.
4. Объединяем результаты фоновых заданий в один главный файл сообщения обмена.
5. Завершаем выгрузку как обычно.
Код главной управляющей процедуры выглядит следующим образом:
Процедура СформироватьСообщениеОбмена()
УзелОбмена = ПланыОбмена.Тестовый.НайтиПоНаименованию("Узел получатель", Истина);
// Получаем список объектов конфигурации, который входят в состав плана обмена
СоставПланаОбмена = Метаданные.ПланыОбмена.Тестовый.Состав;
// Открываем главный файл сообщения обмена
ЗаписьXML = Новый ЗаписьXML();
ПолноеИмяФайла = КаталогСообщений + "\" + Строка(УзелОбмена.УникальныйИдентификатор()) + ".xml";
ЗаписьXML.ОткрытьФайл(ПолноеИмяФайла);
// Создаём объект "ЗаписьСообщенияОбмена" и блокируем узел (объектная блокировка)
ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
ЗаписьСообщения.НачатьЗапись(ЗаписьXML, УзелОбмена);
НомерСообщения = ЗаписьСообщения.НомерСообщения;
// Массив фоновых заданий - по одному на каждый объект конфигурации, который входит в состав плана обмена
СписокФоновыхЗаданий = Новый Массив();
// Количество постоянно активных фоновых заданий.
// Управление их выполнением осуществляется специальными процедурами (см. ниже).
// Обычно их количество должно равняться количеству ядер сервера.
// На практике нужно подбирать их количество в зависимости от загрузки сервера.
// Слишком большое количество фоновых заданий может "положить" сервер.
КоличествоАктивныхЗаданий = 4;
// Цикл формирования списка фоновых задний и их параметров
Для Каждого Элемент Из СоставПланаОбмена Цикл
ИмяЗадания = "Экспорт сообщения № " + Формат(НомерСообщения, "ЧГ=0") + ": " + Элемент.Метаданные.ПолноеИмя();
Параметры = Новый Массив();
Параметры.Добавить(УзелОбмена);
Параметры.Добавить(НомерСообщения);
Параметры.Добавить(Элемент.Метаданные);
ПараметрыФоновогоЗадания = Новый Структура();
ПараметрыФоновогоЗадания.Вставить("ИмяЗадания", ИмяЗадания);
ПараметрыФоновогоЗадания.Вставить("ИмяПроцедуры", "ОбщийМодульОбменаДанными.ВыполнитьВыгрузкуИзмененийОбъектаКонфигурации");
ПараметрыФоновогоЗадания.Вставить("ПараметрыПроцедуры", Параметры);
СписокФоновыхЗаданий.Добавить(ПараметрыФоновогоЗадания);
КонецЦикла;
// Передача списка фоновых заданий управляющим процедурам на выполнение
// Формируем части сообщения обмена по одному файлу для каждого объекта конфигурации
ВыполнитьФоновыеЗадания(СписокФоновыхЗаданий, КоличествоАктивныхЗаданий);
// Собираем все части сообщения обмена вместе в главном файле (см. выше)
СформироватьЕдиноеСообщениеОбмена(СоставПланаОбмена, УзелОбмена, НомерСообщения, ЗаписьXML);
// Завершаем формирование сообщения обмена
ЗаписьСообщения.ЗакончитьЗапись();
КонецПроцедуры
Очень важно ничего не напутать с именованием файлов частей сообщения. Для этого код формирования такого имени вынесен в отдельную функцию. Вот она:
Функция ПолучитьИмяФайлаЧастиСообщенияОбмена(УзелОбмена, НомерСообщения, ОбъектМетаданных)
Возврат КаталогСообщений +
"\" +
Строка(УзелОбмена.УникальныйИдентификатор()) +
"_" +
Формат(НомерСообщения, "ЧГ=0") +
"_" +
ОбъектМетаданных.Имя + ".xml";
КонецФункции
Процедура, которая формирует файл части сообщения:
// Процедура общего модуля для вызова из фонового задания и формирования части сообщения по объекту метаданных
Процедура ВыполнитьВыгрузкуИзмененийОбъектаКонфигурации(УзелОбмена, НомерСообщения, ОбъектМетаданных) Экспорт
Выборка = ПланыОбмена.ВыбратьИзменения(УзелОбмена, НомерСообщения, ОбъектМетаданных);
Если Не Выборка.Следующий() Тогда
Возврат;
КонецЕсли;
ЗаписьXML = Новый ЗаписьXML();
ПолноеИмяФайла = ПолучитьИмяФайлаЧастиСообщенияОбмена(УзелОбмена, НомерСообщения, ОбъектМетаданных);
ЗаписьXML.ОткрытьФайл(ПолноеИмяФайла);
ЗаписатьXML(ЗаписьXML, Выборка.Получить());
Пока Выборка.Следующий() Цикл
ЗаписатьXML(ЗаписьXML, Выборка.Получить());
КонецЦикла;
ЗаписьXML.Закрыть();
КонецПроцедуры
Собираются все части вместе такой процедурой:
Процедура СформироватьЕдиноеСообщениеОбмена(СоставПланаОбмена, УзелОбмена, НомерСообщения, ЗаписьXML)
Для Каждого ОбъектМетаданных Из СоставПланаОбмена Цикл
ПолноеИмяФайла = ПолучитьИмяФайлаЧастиСообщенияОбмена(УзелОбмена, НомерСообщения, ОбъектМетаданных);
Файл = Новый Файл(ПолноеИмяФайла);
Если Файл.Существует() Тогда
ЧтениеТекста = Новый ЧтениеТекста(ПолноеИмяФайла, КодировкаТекста.UTF8);
// Дописываем часть в главный файл сообщения обмена
ЗаписьXML.ЗаписатьБезОбработки(ЧтениеТекста.Прочитать());
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Внимание!
Накладные расходы на создание файлов частей сообщения + последующее их объединение в один главный файл сообщения могут "съесть" всю выгоду от распараллеливания процесса. Это зависит от размеров такого сообщения. Тестируйте код перед его применением!