При разработке внешних обработок и отчетов в управляемом интерфейсе 1С:Предприятие программисты часто сталкиваются с неприятным поведением системы: при выполнении длительного цикла на стороне клиента (например, при обработке большого массива данных или выгрузке в файлы Excel) интерфейс программы визуально «зависает». Любые попытки динамически обновлять заголовки декораций, надписей или менять значения реквизитов формы внутри цикла не дают видимого результата. На экране пользователя отображается статичная картинка, операционная система может пометить окно программы статусом «Не отвечает», а итоговый результат выводится только после полного завершения работы всего алгоритма.
В этой статье мы подробно разберем, почему возникает этот эффект, рассмотрим простейшее штатное решение проблемы, а также проанализируем профессиональные архитектурные альтернативы, которые сделают ваше решение плавным, интерактивным и отказоустойчивым.
Интерфейс платформы 1С:Предприятие работает в однопоточном режиме на клиенте. Когда вы запускаете длительный цикл обработки данных непосредственно в клиентском коде, этот единственный поток полностью резервируется под выполнение математических расчетов или взаимодействие с внешними COM-объектами. Попытка изменить свойство элемента формы, например Элементы.Декорация1.Заголовок = "Обработано...", не приводит к мгновенной перерисовке экрана.
Платформа ставит задачу на обновление графического интерфейса в общую очередь сообщений. Эта очередь начнет обрабатываться только тогда, когда текущая клиентская процедура полностью завершит свою работу и вернет управление платформе. В результате пользователь видит лишь финальное состояние формы, полностью пропустив весь процесс «бегущих циферок».
Разберем детально несколько способов решения этой задачи — от самых простых до максимально надежных и соответствующих стандартам разработки фирмы 1С.
Наиболее быстрым и простым способом показать пользователю текущий прогресс без существенной перестройки кода является использование глобального метода Состояние(). Этот метод напрямую обращается к платформе 1С, заставляя её принудительно обновить строку состояния приложения (в зависимости от настроек интерфейса — в нижней панели или в виде всплывающего системного окна «Такси»), минуя стандартную очередь обновления элементов формы.
Синтаксис метода выглядит следующим образом:
Состояние(Текст, Прогресс, Пояснение, Картинка)
Проанализируем пример практической реализации этого метода в цикле:
&НаКлиенте
Процедура ВыполнитьОбработкуДанных()
МассивДанных = ПолучитьДанныеССервера(); // Получаем наши 3000 позиций
ВсегоЭлементов = МассивДанных.Количество();
Для Счетчик = 1 По ВсегоЭлементов Цикл
// Здесь выполняется полезное действие, например, запись строки в Excel
ОбработатьСтрокуДанных(МассивДанных[Счетчик - 1]);
// Оптимизированный вывод состояния
Если Счетчик % 50 = 0 Или Счетчик = ВсегоЭлементов Тогда
ПроцентВыполнения = Окр((Счетчик / ВсегоЭлементов) * 100);
Состояние("Выполняется выгрузка данных...", ПроцентВыполнения, "Обработано позиций: " + Счетчик + " из " + ВсегоЭлементов);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Важный нюанс оптимизации: Обратите внимание на условие Счетчик % 50 = 0. Вызывать метод Состояние() на каждой итерации цикла (все 3000 раз) — крайне неэффективно. Постоянное обновление системного интерфейса создает дополнительную нагрузку на процессор и может существенно замедлить общую скорость выполнения алгоритма. Оптимальным решением является обновление прогресс-бара раз в 10, 50 или 100 итераций.
Если вам необходимо выводить информацию о прогрессе непосредственно на форму (например, в созданное поле индикатора с видом Поле индикатора или в декорацию-надпись), метод Состояние() не подойдет, так как он выводит данные только в системное окно платформы.
Чтобы заставить элементы формы обновляться в реальном времени, мы должны периодически возвращать управление платформе, прерывая выполнение цикла. Этот подход называется квантованием процесса. Мы делим массив данных на порции (кванты) и обрабатываем их последовательно, используя механизм обработчиков ожидания.
Рассмотрим, как реализовать этот алгоритм по шагам:
МассивДанных (произвольный тип), ТекущийИндекс (Число) и ВсегоЭлементов (Число).ИндикаторПрогресса (Число) и перетащим его на форму, установив свойство Вид в значение Поле индикатора.Посмотрим на пример кода, реализующий данную схему:
&НаКлиенте
Перем ЭкспортныйМассивДанных;
Перем ТекущийИндексОбработки;
Перем ОбщееКоличествоЗаписей;
&НаКлиенте
Процедура НачатьКвантованнуюОбработку(Команда)
// Инициализируем данные для обработки
ЭкспортныйМассивДанных = ПолучитьДанныеССервера();
ОбщееКоличествоЗаписей = ЭкспортныйМассивДанных.Количество();
ТекущийИндексОбработки = 0;
// Настраиваем шкалу индикатора на форме
Элементы.ИндикаторПрогресса.МинимальноеЗначение = 0;
Элементы.ИндикаторПрогресса.МаксимальноеЗначение = ОбщееКоличествоЗаписей;
Элементы.ИндикаторПрогресса.Значение = 0;
// Подключаем однократный обработчик ожидания с интервалом 0.1 секунды
ПодключитьОбработчикОжидания("ОбработатьПорциюДанных", 0.1, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьПорциюДанных()
РазмерПорции = 100; // Определяем, сколько элементов обработаем за один раз
ПределПорции = Мин(ТекущийИндексОбработки + РазмерПорции, ОбщееКоличествоЗаписей);
Пока ТекущийИндексОбработки < ПределПорции Цикл
ТекущийЭлемент = ЭкспортныйМассивДанных[ТекущийИндексОбработки];
// Выполняем обработку элемента
ОбработатьСтрокуДанных(ТекущийЭлемент);
ТекущийИндексОбработки = ТекущийИндексОбработки + 1;
КонецЦикла;
// Обновляем элементы формы в реальном времени
Элементы.ИндикаторПрогресса.Значение = ТекущийИндексОбработки;
Элементы.ДекорацияСтатус.Заголовок = "Обработано элементов: " + ТекущийИндексОбработки + " из " + ОбщееКоличествоЗаписей;
// Проверяем, остались ли необработанные данные
Если ТекущийИндексОбработки < ОбщееКоличествоЗаписей Тогда
// Снова подключаем обработчик для следующей порции
ПодключитьОбработчикОжидания("ОбработатьПорциюДанных", 0.1, Истина);
Иначе
ПоказатьПредупреждение(, "Обработка данных завершена успешно!");
КонецЕсли;
КонецПроцедуры
Преимущества квантования: Интерфейс программы остается полностью отзывчивым. Пользователь видит плавное заполнение индикатора на форме, бегущие цифры в надписи и может без проблем свернуть окно программы или даже нажать кнопку «Отмена», если мы предусмотрим соответствующий флаг прерывания в коде.
Согласно стандартам разработки фирмы 1С, любые ресурсоемкие операции, выполнение которых занимает более 8-10 секунд, категорически не рекомендуется выполнять непосредственно на клиенте. В веб-клиенте браузер может выдать предупреждение о зависшем скрипте или разорвать соединение по таймауту.
Правильным архитектурным решением является перенос тяжелых расчетов на сервер и выполнение их в рамках **фонового задания**. Библиотека Стандартных Подсистем (БСП) предоставляет готовые механизмы для этого.
Давайте проанализируем, как выглядит эта схема при интеграции с БСП:
ДлительныеОперации.ВыполнитьВФоне(), передавая туда имя экспортной серверной процедуры, которая будет обрабатывать данные.ДлительныеОперации.СообщитьПрогресс(), передавая туда процент выполнения и структуру с дополнительным описанием.ДлительныеОперацииКлиент.ОжидатьЗавершение(). Эта форма автоматически показывает красивый индикатор, обновляет текст состояния на основе данных с сервера и предоставляет пользователю кнопку для отмены операции.Этот подход гарантирует стабильность работы системы, независимость от клиентской операционной системы и полное соответствие лучшим практикам разработки на платформе 1С:Предприятие.
Если первоначальная задача автора заключалась в сохранении большого объема данных в файл EXCEL, то главная причина долгой работы и зависаний, скорее всего, кроется в использовании COM-объекта на клиенте (Новый COMОбъект("Excel.Application")). Передача каждого значения ячейки через COM-соединение во внешнее приложение Windows выполняется крайне медленно.
Проанализируем более производительное альтернативное решение: формировать всю таблицу на сервере в виде стандартного объекта ТабличныйДокумент, а затем сохранять его напрямую в формат XLSX с помощью встроенного метода платформы:
&НаСервереБезКонтекста
Функция СформироватьФайлExcelНаСервере(МассивДанных)
ТабДок = Новый ТабличныйДокумент;
// Заполняем строки табличного документа на сервере (работает мгновенно в памяти)
Для Каждого Строка Из МассивДанных Цикл
// Добавление областей и заполнение параметров
// ...
КонецЦикла;
// Сохраняем табличный документ напрямую в формат XLSX на сервере
ПутьКФайлу = ПолучитьИмяВременногоФайла("xlsx");
ТабДок.Записать(ПутьКФайлу, ТипФайлаТабличногоДокумента.XLSX);
// Помещаем готовый файл во временное хранилище
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлу);
АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанныеФайла);
// Удаляем временный файл с сервера
УдалитьФайлы(ПутьКФайлу);
Возврат АдресХранилища;
КонецФункции
На клиенте вам останется лишь получить этот файл из временного хранилища одной строкой кода с помощью метода ПолучитьФайл(). Процесс генерации файла на сервере без COM-соединения отработает в десятки раз быстрее, избавив вас от необходимости организовывать сложные индикаторы прогресса для длительных клиентских циклов.