При работе с большими объемами данных, например, когда необходимо обработать более 30 000 документов (Реализаций товаров и услуг), сформировать для них печатные формы, подписать их на стороне клиента локальным сертификатом и передать обратно на сервер, разработчики часто сталкиваются с проблемой асинхронности. В управляемом приложении 1С методы для работы с файлами работают в асинхронном режиме. Это приводит к тому, что основной код процедуры завершается до того, как файлы фактически будут переданы на сервер. В результате форма разблокируется, пользователь может ее закрыть, а процесс загрузки прервется или приведет к ошибкам. В данной статье мы подробно разберем, как решить эту задачу, обеспечив стабильность и контроль процесса.
Рассмотрим стандартную ситуацию: мы используем метод НачатьПомещениеФайлаНаСервер. Поскольку этот метод не останавливает выполнение основного потока кода, процедура, инициировавшая загрузку, завершается мгновенно. Пользователь видит доступную форму и может совершить действия, которые нарушат логику обработки. Для предотвращения таких ситуаций нам необходимо внедрить механизм контроля состояния формы.
Проанализируем решение через использование переменной-флага. В модуле формы мы определим реквизит (или переменную) ПроцессЗагрузкиИдет типа Булево. Перед началом асинхронной операции мы устанавливаем это значение в Истина, а в обработчике завершения — в Ложь.
Для того чтобы пользователь не закрыл форму в процессе, мы переопределим обработчик события ПередЗакрытием на клиенте:
&НаКлиенте
Процедура ПередЗакрытием(Отказ, СтандартнаяОбработка)
Если ПроцессЗагрузкиИдет Тогда
Отказ = Истина;
ПоказатьПредупреждение(, "Пожалуйста, дождитесь завершения загрузки и обработки файлов!");
КонецЕсли;
КонецПроцедуры
Разберем, как это работает вместе с методом загрузки. Когда мы вызываем НачатьПомещениеФайловНаСервер, мы передаем в него ОписаниеОповещения. Только в процедуре, указанной в этом описании, мы сбросим наш флаг блокировки.
Одной из главных ошибок при массовой обработке является попытка запустить цикл по всем 30 000 файлам сразу. Это создает огромную нагрузку на сетевой стек и может привести к переполнению стека вызовов. Вместо этого мы рекомендуем использовать алгоритм "Очереди блоков".
Выясним причину, почему блоки так важны. При передаче файлов через временное хранилище, данные занимают оперативную память рабочего процесса rphost. Передача 3 ГБ данных одним пакетом может привести к падению сервера или критическому замедлению системы. Разделим наши 30 000 документов на блоки, например, по 500 или 1000 штук.
Рассмотрим структуру алгоритма обработки блока:
МенеджерКриптографии.Посмотрим на пример кода, который реализует последовательную обработку блоков без рекурсии через обработчик ожидания:
&НаКлиенте
Перем ТекущийБлок;
&НаКлиенте
Процедура НачатьОбработкуОчереди()
ПроцессЗагрузкиИдет = Истина;
ТекущийБлок = 1;
ВыполнитьОбработкуТекущегоБлока();
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьОбработкуТекущегоБлока()
// Логика подготовки файлов блока
// ...
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеЗагрузкиБлока", ЭтотОбъект);
// Помещаем файлы текущего блока на сервер
НачатьПомещениеФайловНаСервер(ОписаниеОповещения, , , , СписокФайлов, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗагрузкиБлока(Результат, ДополнительныеПараметры) Экспорт
Если Результат = Неопределено Тогда
ПроцессЗагрузкиИдет = Ложь;
Возврат;
КонецЕсли;
// Вызываем серверный метод для обработки загруженных файлов
ОбработатьФайлыНаСервере(ТекущийБлок);
// Проверяем, есть ли еще блоки
Если ЕстьЕщеДанные(ТекущийБлок) Тогда
ТекущийБлок = ТекущийБлок + 1;
// Используем небольшую паузу, чтобы очистить стек и дать интерфейсу "вздохнуть"
ПодключитьОбработчикОжидания("ВыполнитьОбработкуТекущегоБлока", 0.1, Истина);
Иначе
ПроцессЗагрузкиИдет = Ложь;
Сообщить("Все документы успешно обработаны!");
КонецЕсли;
КонецПроцедуры
После того как блок файлов попал на сервер, нам нужно связать их с исходными документами в табличной части формы. Проанализируем ситуацию: в табличной части 30 000 строк. Использовать метод НайтиСтроки для каждой ссылки в цикле может быть накладно, но это наиболее штатный путь для управляемых форм.
Для оптимизации мы можем использовать колонку НомерБлока. Рассмотрим серверный метод обработки:
&НаСервере
Процедура ОбработатьФайлыНаСервере(НомерБлока)
ПараметрыОтбора = Новый Структура("НомерБлока", НомерБлока);
НайденныеСтроки = Объект.ДокументыКОбработке.НайтиСтроки(ПараметрыОтбора);
Для Каждого СтрокаТЧ Из НайденныеСтроки Цикл
// Создаем связанный объект ЭДО
НовыйЭДО = Документы.ЭлектронныйДокумент.СоздатьДокумент();
// Заполняем реквизиты и прикрепляем файл из временного хранилища
// ...
НовыйЭДО.Записать();
// Сохраняем ссылку в ТЧ для отображения пользователю
СтрокаТЧ.СсылкаЭДО = НовыйЭДО.Ссылка;
КонецЦикла;
КонецПроцедуры
При реализации данного механизма крайне важно учитывать следующие аспекты, которые помогут избежать падения системы:
УникальныйИдентификаторФормы данные удалятся только при закрытии формы. Для 30 000 файлов это может привести к нехватке памяти. Рекомендуется использовать "короткое" хранилище или принудительно очищать объекты после обработки блока.MaxAllowedContentLength в IIS или LimitRequestBody в Apache. Если пакет из 2000 PDF-файлов превысит эти лимиты, загрузка оборвется с ошибкой соединения.ОписаниеОповещения частично решает эту проблему, давая приложению возможность обрабатывать сообщения очереди между блоками.ДлительныеОперации. Она позволяет вывести штатное окно прогресса с кнопкой "Отмена" и индикатором выполнения (например, "Обработано 1500 из 30000"). Это выглядит более профессионально и избавляет от ручного управления флагами блокировки.Подводя итог, можно сказать, что ключом к успешной загрузке огромного массива файлов является разделение на блоки, разрыв цепочки вызовов через обработчик ожидания и строгий контроль состояния интерфейса через события формы. Такой подход гарантирует, что даже при слабом интернет-соединении или высокой загрузке сервера все документы будут подписаны и обработаны корректно.