При импорте больших объемов данных из внешних файлов в 1С:Предприятие 8.3 разработчики часто совершают классическую ошибку: пытаются обработать файл построчно на клиенте, полагая, что передача тяжелого файла на сервер вызовет существенные задержки. В реальности такой подход приводит к полной блокировке интерфейса пользователя, критическому перерасходу оперативной памяти и падению сеанса по ошибке недостатка памяти.
Давайте подробно разберем, какие физические процессы происходят в платформе 1С при попытке разобрать крупный файл на клиенте:
Структура быстро переполнит выделенную память, вызвав аварийное закрытие процесса.Если объем данных действительно невелик (до нескольких сотен строк) и вы абсолютно уверены в стабильности клиентской машины, можно воспользоваться накоплением данных в памяти клиента. Поскольку объект ТаблицаЗначений в управляемом приложении на клиенте недоступен, оптимальным решением является использование массива структур.
Давайте посмотрим на практический пример создания такой структуры:
МассивРезультат = Новый Массив;
// Обрабатываем строки во временном цикле
Для Счетчик = 1 По 100 Цикл
НоваяСтрока = Новый Структура;
НоваяСтрока.Вставить("Дата", ТекущаяДата());
НоваяСтрока.Вставить("Действие", "прыг");
НоваяСтрока.Вставить("Тип", "сильно");
НоваяСтрока.Вставить("Количество", Счетчик);
НоваяСтрока.Вставить("ВремяОкончания", "04:12");
МассивРезультат.Добавить(НоваяСтрока);
КонецЦикла;
Полученный МассивРезультат можно легко передать в качестве параметра на сервер. Однако, если количество строк измеряется тысячами, забудьте про этот метод и используйте исключительно серверную обработку.
Для эффективной и быстрой обработки больших файлов мы должны разделить задачу на три последовательных этапа. Рассмотрим их детально.
Современные стандарты разработки 1С (начиная с версии платформы 8.3.15) запрещают использование синхронных методов для работы с файлами. Для передачи файла с клиента на сервер используется асинхронный неконтекстный метод НачатьПомещениеФайлаНаСервер().
Разберем, как правильно реализовать интерактивный выбор файла на клиенте и его отправку на сервер:
&НаКлиенте
Процедура НачатьЗагрузкуФайла(Команда)
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеЗагрузкиФайлаНаСервер", ЭтотОбъект);
ПараметрыЗагрузки = Новый ПараметрыПомещенияФайла;
ПараметрыЗагрузки.Интерактивно = Истина;
ПараметрыЗагрузки.Заголовок = "Выберите файл для импорта";
НачатьПомещениеФайлаНаСервер(ОписаниеОповещения, , , ПараметрыЗагрузки, УникальныйИдентификатор);
КонецПроцедуры
В результате вызова этой функции платформа откроет стандартный диалог выбора файла. После выбора файл будет передан на сервер, а управление перейдет к процедуре обратного вызова:
&НаКлиенте
Процедура ПослеЗагрузкиФайлаНаСервер(Результат, АдресХранилища, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
Если Результат Тогда
// Файл успешно помещен во временное хранилище
ОбработатьФайлНаСервере(АдресХранилища);
ПоказатьПредупреждение(, "Файл успешно обработан!");
КонецЕсли;
КонецПроцедуры
Когда файл загружен на сервер, нам необходимо прочитать его содержимое. Распространенная ошибка на этом этапе — использование объекта ТекстовыйДокумент. Метод Прочитать() загружает весь файл целиком в оперативную память сервера, что при больших объемах (от 50 Мб) вызывает резкий скачок потребления ОЗУ процессом rphost.
Для предотвращения утечек памяти мы применим объект ЧтениеТекста. Этот объект считывает файл последовательно, строка за строкой, удерживая в памяти только текущую обрабатываемую строку. Это гарантирует стабильную работу сервера независимо от размера файла.
Проанализируем пример серверной процедуры, которая получает файл из временного хранилища, сохраняет его во временную папку сервера и читает построчно:
&НаСервере
Процедура ОбработатьФайлНаСервере(АдресХранилища)
// Получаем двоичные данные из хранилища
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресХранилища);
// Создаем временный файл на диске сервера
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("csv");
ДвоичныеДанные.Записать(ИмяВременногоФайла);
// Инициализируем объект ЧтениеТекста
ФайлЧтения = Новый ЧтениеТекста;
ФайлЧтения.Открыть(ИмяВременногоФайла, КодировкаТекста.UTF8);
// Начинаем транзакцию для ускорения записи документов в СУБД
НачатьТранзакцию();
СчетчикСтрок = 0;
СтрокаДанных = ФайлЧтения.ПрочитатьСтроку();
Пока СтрокаДанных <> Неопределено Цикл
// Разбираем строку по разделителю (например, точка с запятой)
МассивЧастей = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(СтрокаДанных, ";");
Если МассивЧастей.Количество() >= 5 Тогда
// Пример создания документа на основании строки
НовыйДокумент = Документы.Действие.СоздатьДокумент();
НовыйДокумент.Дата = ТекущаяДата();
НовыйДокумент.ВидДействия = МассивЧастей[1];
НовыйДокумент.Тип = МассивЧастей[2];
НовыйДокумент.Количество = Число(МассивЧастей[3]);
// Записываем документ
НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
СчетчикСтрок = СчетчикСтрок + 1;
// Разделяем запись на порции для оптимизации транзакций
Если СчетчикСтрок % 200 = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЕсли;
// Читаем следующую строку
СтрокаДанных = ФайлЧтения.ПрочитатьСтроку();
КонецЦикла;
// Фиксируем оставшиеся документы
Если ТранзакцияАктивна() Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
// Закрываем чтение файла
ФайлЧтения.Закрыть();
// Очищаем временные файлы на сервере
Попытка
УдалитьФайлы(ИмяВременногоФайла);
Исключение
// Обработка исключения при удалении
КонецПопытки;
УдалитьИзВременногоХранилища(АдресХранилища);
КонецПроцедуры
Если количество создаваемых документов исчисляется десятками тысяч, процесс записи даже при использовании транзакций может занять продолжительное время. В таком случае выполнение кода в контексте сеанса пользователя заблокирует интерфейс.
Для решения этой проблемы мы рекомендуем запускать процедуру обработки на сервере в виде фонового задания с помощью менеджера фоновых заданий ФоновыеЗадания.Выполнить(). На клиенте в это время запускается периодический обработчик ожидания ПодключитьОбработчикОжидания(), который опрашивает статус фонового задания и информирует пользователя о проценте выполнения. Это позволяет пользователю продолжать комфортную работу в программе, пока система загружает данные в фоновом режиме.