При разработке тяжелых отчетов и обработок в платформах 1С часто возникает необходимость выносить длительные расчеты в фоновые задания, чтобы не блокировать интерфейс пользователя. Однако при попытке вернуть результат работы из фонового задания в виде таблицы значений и вывести его на форму разработчики сталкиваются с длительным «зависанием» клиентского сеанса. Проанализируем причины этой проблемы и рассмотрим эффективные варианты вывода больших массивов данных.
Рассмотрим типичную схему работы: фоновое задание формирует объект типа ТаблицаЗначений, помещает его во временное хранилище, после чего клиентский сеанс забирает адрес хранилища и загружает данные в элемент формы.
Посмотрим на пример кода серверной процедуры, которая обычно вызывает задержку:
&НаСервере
Процедура ПолучитьТаблицуПоРезультатамФоновогоЗадания(АдресТаблицы)
// Получение ТЗ из хранилища и загрузка в коллекцию формы
ТаблицаНаФорме.Загрузить(ПолучитьИзВременногоХранилища(АдресТаблицы));
КонецПроцедуры
Хотя сам код выглядит минималистично, на практике выполнение процедуры для десятков и сотен тысяч строк занимает десятки секунд. Разберем, почему это происходит:
ПолучитьИзВременногоХранилища() из другого сеанса платформа не передает ссылку на объект в памяти, а выполняет полноценную сериализацию данных в хранилище и их десериализацию.ДанныеФормыКоллекция — это не обычная ТаблицаЗначений. Данный объект хранит дополнительную служебную информацию (состояние каждой строки, связи данных, механизмы отслеживания изменений). При вызове метода Загрузить() платформа конвертирует каждую строку в контекст формы.ДанныеФормыКоллекция передается с сервера на тонкий клиент. Интерфейсный поток приложения полностью блокируется, пока графический движок 1С создает элементы управления и ячейки для всех строк.Важно понимать ключевое ограничение платформы: **напрямую передать элементы формы или реквизит контекста формы в фоновое задание невозможно**, так как фоновое задание не имеет клиентского контекста и пользовательского интерфейса.
Наиболее эффективным решением при работе с отчетами является отказ от использования табличной части или таблицы значений на форме в пользу объекта ТабличныйДокумент.
Рассмотрим преимущества вывода данных в ТабличныйДокумент:
ТабличныйДокумент значительно «легче» для контекста тонкого клиента, так как не создает сложную структуру объектов ДанныеФормыЭлементКоллекции для каждой строки.Если специфика задачи позволяет отображать результат в виде печатной формы или отчета, следует формировать ТабличныйДокумент непосредственно в фоновом задании, помещать его в хранилище и выводить на форме в элемент поля табличного документа.
Если пользователю необходима именно интерактивная таблица с возможностью отбора, сортировки и перехода к объектам, вместо таблицы значений рекомендуем использовать объект ДинамическийСписок.
Проанализируем алгоритм реализации данного подхода:
ИдентификаторПользователя или ИдентификаторЗадания с типом УникальныйИдентификатор.ДинамическийСписок, запросом выбирающий данные из этого регистра по конкретному идентификатору.Главный плюс этого способа — **виртуализация данных**. ДинамическийСписок считывает и передает на тонкий клиент только те строки, которые в данный момент видны на экране пользователя, благодаря чему интерфейс открывается мгновенно при любом объеме строк.
Если данные необходимо обрабатывать в таблице значений, но их объем слишком велик, разберем способ постраничной подгрузки.
Суть метода заключается в том, что фоновое задание сохраняет полную ТаблицуЗначений во временном хранилище (или файле на сервере), а процедура формы запрашивает и загружает данные небольшими порциями (например, по 100–200 строк).
Посмотрим на пример функции выбора порции данных на сервере:
&НаСервере
Процедура ЗагрузитьСтраницуДанных(АдресХранилища, НомерСтраницы, РазмерСтраницы)
ТЗПолная = ПолучитьИзВременногоХранилища(АдресХранилища);
Если ТЗПолная = Неопределено Тогда
Возврат;
КонецЕсли;
// Очищаем текущие данные на форме
ТаблицаНаФорме.Очистить();
НачальныйИндекс = (НомерСтраницы - 1) * РазмерСтраницы;
КонечныйИндекс = Мин(НачальныйИндекс + РазмерСтраницы - 1, ТЗПолная.Количество() - 1);
Для Индекс = НачальныйИндекс По КонечныйИндекс Доклопать
ЗаполнитьЗначенияСвойств(ТаблицаНаФорме.Добавить(), ТЗПолная[Индекс]);
КонецЦикла;
КонецПроцедуры
Пользователю предоставляются кнопки навигации («Вперед», «Назад») или подгрузка данных реализуется при прокрутке, что исключает передачи огромных массивов контекста за один вызов.
В случаях, когда отказаться от загрузки большого массива строк в ДанныеФормыКоллекция невозможно, уменьшить задержку вызова поможет временное отключение отображения элемента формы.
Рассмотрим порядок действий:
&НаСервере
Процедура БыстраяЗагрузкаВКоллекцию(АдресТаблицы)
// 1. Отключаем видимость элемента таблицы
Элементы.ТаблицаНаФорме.Видимость = Ложь;
// 2. Выполняем загрузку данных
ДанныеИзХранилища = ПолучитьИзВременногоХранилища(АдресТаблицы);
Если ТипЗнч(ДанныеИзХранилища) = Тип("ТаблицаЗначений") Тогда
ЗначениеВРеквизитФормы(ДанныеИзХранилища, "ТаблицаНаФорме");
КонецЕсли;
// 3. Включаем видимость обратно
Элементы.ТаблицаНаФорме.Видимость = Истина;
КонецПроцедуры
Скрытие элемента управления Элементы.ТаблицаНаФорме.Видимость = Ложь перед вызовом ЗначениеВРеквизитФормы() или Загрузить() позволяет платформе не тратить ресурсы на перерисовывание интерфейса для каждой вставляемой строки во время выполнения серверного вызова.