Как ускорить чтение CSV-файлов в клиент-серверном режиме 1С и настроить индикацию прогресса?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе в файловом режиме «1С:Предприятия» многие операции выполняются быстро и незаметно для пользователя. Однако при переходе в клиент-серверный режим разработчики часто сталкиваются с резким падением производительности привычных алгоритмов. Типичный пример — чтение текстовых файлов (в том числе формата CSV) объемом в несколько мегабайт, которое на сервере начинает занимать часы вместо секунд. В этой статье мы разберем причины этой проблемы и пошагово реализуем максимально эффективную схему чтения данных, а также настроим удобную индикацию выполнения для пользователя.

Выясняем причину: почему чтение на клиенте работает медленно?

Обратим внимание на исходный код, который часто становится источником проблем:


&НаКлиенте
Процедура РазобратьCSV()
    Текст = Новый ЧтениеТекста(ФайлCSV, КодировкаТекста.ANSI);
    Для СтрокаCSV = 1 По КоличествоСтрок Цикл
        Строка = Текст.ПрочитатьСтроку();
        // Внутри этого цикла происходит обработка и вызов серверных процедур
        ЗаписатьСтрокуНаСервере(Строка);
    КонецЦикла;
КонецПроцедуры

Основная ошибка здесь заключается в архитектурном «грехе» клиент-серверного взаимодействия — избыточных вызовах сервера в цикле (так называемый latency-эффект). Когда процедура выполняется на клиенте, а чтение или запись данных происходят в цикле по десяткам тысяч строк, программа вынуждена совершать сетевой запрос туда и обратно на каждую итерацию. Даже если задержка сети составляет всего 2-5 миллисекунд, при 30 000 строк суммарное время ожидания составит несколько минут или даже часов.

Для оптимизации этого процесса необходимо придерживаться фундаментального правила разработки в 1С: минимизировать количество переходов между клиентом и сервером. Файл должен быть передан на сервер целиком, и вся его обработка должна происходить в один сеанс работы серверного контекста.

Решение проблемы: правильный перенос обработки на сервер

Рассмотрим детально, как правильно организовать процесс загрузки файла в клиент-серверном режиме. Нам потребуется передать локальный файл с компьютера пользователя во ВременноеХранилище, а затем прочитать его на стороне сервера.

Разберем по шагам реализацию этого механизма:

  1. На клиенте получаем локальный путь к файлу и помещаем его во временное хранилище платформы.
  2. Передаем полученный адрес хранилища в серверную процедуру.
  3. На сервере извлекаем двоичные данные файла, сохраняем их во временный файл операционной системы сервера и выполняем быстрое чтение без сетевых задержек.

Посмотрим на пример реализации кода:


&НаКлиенте
Процедура ЗагрузитьФайлИОбработать()
    
    // Выбираем файл на компьютере пользователя
    ДиалогВыбора = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
    ДиалогВыбора.Фильтр = "Файлы данных (*.csv)|*.csv";
    
    Если ДиалогВыбора.Выбрать() Тогда
        ПутьКФайлу = ДиалогВыбора.ПолноеИмяФайла;
        
        // Помещаем файл во временное хранилище
        АдресХранилища = "";
        ПоместитьФайл(АдресХранилища, ПутьКФайлу, , Ложь, УникальныйИдентификатор);
        
        // Передаем управление на сервер ОДНИМ вызовом
        ОбработатьФайлНаСервере(АдресХранилища);
        
        ПоказатьПредупреждение(, "Файл успешно обработан!");
    КонецЕсли;
    
КонецПроцедуры

Теперь напишем серверную часть, которая будет работать непосредственно на стороне сервера приложений «1С:Предприятие»:


&НаСервереБезКонтекста
Процедура ОбработатьФайлНаСервере(Знач АдресХранилища)
    
    // Извлекаем двоичные данные файла из временного хранилища
    ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресХранилища);
    
    // Получаем безопасный путь во временном каталоге сервера
    ИмяВременногоФайла = ПолучитьИмяВременногоФайла("csv");
    ДвоичныеДанные.Записать(ИмяВременногоФайла);
    
    // Быстрое чтение файла на сервере
    ТекстФайла = Новый ЧтениеТекста(ИмяВременногоФайла, КодировкаТекста.ANSI);
    
    ТаблицаДляЗаписи = ИнициализироватьТаблицуДанных();
    
    ТекущаяСтрока = ТекстФайла.ПрочитатьСтроку();
    Пока ТекущаяСтрока <> Неопределено Цикл
        
        // Парсим строку и добавляем в коллекцию для массовой записи
        РазложитьИДобавитьСтроку(ТекущаяСтрока, ТаблицаДляЗаписи);
        
        ТекущаяСтрока = ТекстФайла.ПрочитатьСтроку();
    КонецЦикла;
    
    ТекстФайла.Закрыть();
    
    // Массовая запись данных в БД в одной транзакции
    ЗаписатьДанныеВБазу(ТаблицаДляЗаписи);
    
    // Удаляем временный файл с диска сервера
    Попытка
        УдалитьФайлы(ИмяВременногоФайла);
    Исключение
        ЗаписьЖурналаРегистрации("ОчисткаВременныхФайлов", УровеньЖурналаРегистрации.Предупреждение, , , 
            "Не удалось удалить временный файл: " + ИмяВременногоФайла);
    КонецПопытки;
    
КонецПроцедуры

Такой подход сокращает время выполнения с нескольких часов до считанных секунд, так как все дисковые операции и транзакции к базе данных происходят внутри локальной сети сервера приложений и СУБД.

Организация индикации прогресса длительных операций

После переноса кода на сервер возникает новый вопрос: как показать пользователю, на каком этапе находится загрузка? Ведь пока выполняется серверный вызов, клиентский интерфейс «замораживается», ожидая ответа. Использование стандартных оповещений внутри серверного цикла снова приведет к замедлению работы из-за постоянного переключения контекстов.

Наиболее надежный и современный способ решения этой задачи в управляемом приложении — использование фоновых заданий в связке с временным хранилищем или стандартными механизмами Библиотеки Стандартных Подсистем (БСП).

Проанализируем алгоритм реализации индикации без использования БСП:

  1. Клиент запускает фоновое задание, передавая ему файл, и сразу возвращает управление пользователю.
  2. Серверное фоновое задание обрабатывает файл и периодически (например, каждые 500 строк) записывает текущий процент выполнения во ВременноеХранилище.
  3. На клиенте работает цикличный обработчик ожидания, который раз в 1-2 секунды считывает процент из хранилища и обновляет индикатор на форме.

Рассмотрим программную реализацию этой схемы.

Код серверной процедуры, которая будет запущена в фоновом задании:


// Процедура общего модуля (должна быть доступна для фоновых заданий)
Процедура ВыполнитьИмпортФайлаВФоне(АдресХранилища, АдресПрогресса) Экспорт
    
    ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресХранилища);
    ИмяВременногоФайла = ПолучитьИмяВременногоФайла("csv");
    ДвоичныеДанные.Записать(ИмяВременногоФайла);
    
    // Предварительно подсчитаем строки для расчета процента
    ВсегоСтрок = 0;
    ТекстПодсчет = Новый ЧтениеТекста(ИмяВременногоФайла);
    Пока ТекстПодсчет.ПрочитатьСтроку() <> Неопределено Цикл
        ВсегоСтрок = ВсегоСтрок + 1;
    КонецЦикла;
    ТекстПодсчет.Закрыть();
    
    // Основное чтение и обработка
    Текст = Новый ЧтениеТекста(ИмяВременногоФайла, КодировкаТекста.ANSI);
    ТекущаяСтрока = Текст.ПрочитатьСтроку();
    НомерСтроки = 0;
    
    Пока ТекущаяСтрока <> Неопределено Цикл
        НомерСтроки = НомерСтроки + 1;
        
        ОбработатьСтрокуДанных(ТекущаяСтрока);
        
        // Каждые 100 строк обновляем прогресс
        Если НомерСтроки % 100 = 0 Или НомерСтроки = ВсегоСтрок Тогда
            Процент = Окр((НомерСтроки / ВсегоСтрок) * 100);
            СостояниеПроцесса = Новый Структура;
            СостояниеПроцесса.Вставить("Процент", Процент);
            СостояниеПроцесса.Вставить("Статус", "Обработано строк: " + НомерСтроки + " из " + ВсегоСтрок);
            
            ПоместитьВоВременноеХранилище(СостояниеПроцесса, АдресПрогресса);
        КонецЕсли;
        
        ТекущаяСтрока = Текст.ПрочитатьСтроку();
    КонецЦикла;
    
    Текст.Закрыть();
    УдалитьФайлы(ИмяВременногоФайла);
    
КонецПроцедуры

Код модуля управляемой формы для запуска и отслеживания процесса:


&НаКлиенте
Перем ИдентификаторЗадания;
&НаКлиенте
Перем АдресПрогресса;

&НаКлиенте
Процедура ЗапуститьИмпортСПрогрессом()
    
    // ... выбор файла пользователем и помещение в АдресХранилища ...
    
    // Создаем уникальный адрес для хранения прогресса
    АдресПрогресса = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
    
    // Запускаем фоновое задание на сервере
    ПараметрыЗапуска = Новый Массив;
    ПараметрыЗапуска.Добавить(АдресХранилища);
    ПараметрыЗапуска.Добавить(АдресПрогресса);
    
    ИдентификаторЗадания = ЗапуститьФоновоеЗаданиеНаСервере(ПараметрыЗапуска);
    
    // Подключаем обработчик ожидания для обновления формы
    ПодключитьОбработчикОжидания("ПроверитьСтатусИмпорта", 1); // проверка каждую секунду
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция ЗапуститьФоновоеЗаданиеНаСервере(ПараметрыЗапуска)
    ФоновоеЗадание = ФоновыеЗадания.Выполнить("ИмяОбщегоМодуля.ВыполнитьИмпортФайлаВФоне", ПараметрыЗапуска);
    Возврат ФоновоеЗадание.УникальныйИдентификатор;
КонецФункции

&НаКлиенте
Процедура ПроверитьСтатусИмпорта()
    
    // Получаем текущее состояние из хранилища
    ДанныеПрогресса = ПолучитьДанныеПрогресса(АдресПрогресса);
    
    Если ДанныеПрогресса <> Неопределено Тогда
        // Обновляем реквизиты формы, связанные с графическим индикатором
        Элементы.ИндикаторПрогресса.Значение = ДанныеПрогресса.Процент;
        Элементы.НадписьСтатуса.Заголовок = ДанныеПрогресса.Статус;
    КонецЕсли;
    
    // Проверяем, завершилось ли фоновое задание
    Если ЗаданиеЗавершено(ИдентификаторЗадания) Тогда
        ОтключитьОбработчикОжидания("ПроверитьСтатусИмпорта");
        ПоказатьПредупреждение(, "Загрузка завершена успешно!");
    КонецЕсли;
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьДанныеПрогресса(Знач Адрес)
    Возврат ПолучитьИзВременногоХранилища(Адрес);
EndФункции

&НаСервереБезКонтекста
Функция ЗаданиеЗавершено(Знач Идентификатор)
    Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
    Возврат (Задание <> Неопределено И Задание.Состояние <> СостояниеФоновогоЗадания.Активно);
КонецФункции

Использование готового функционала БСП

Если ваша конфигурация разработана на базе типовых решений от фирмы 1С, вам не нужно писать код отслеживания фоновых заданий вручную. В Библиотеке Стандартных Подсистем реализован мощный механизм длительных операций.

Достаточно запустить операцию через процедуру ДлительныеОперации.ЗапуститьВыполнениеВФоне(), передав ей имя экспортной процедуры. Система БСП сама откроет стандартную форму ожидания с анимированным индикатором прогресса и кнопкой отмены процесса, избавив вас от необходимости проектирования интерфейса вручную.

← На главную