Как ускорить загрузку и заполнение дерева значений на управляемой форме в 1С?

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

Работа с иерархическими структурами данных в платформе «1С:Предприятие 8» — стандартная задача для разработчика. Однако при переходе на управляемое приложение многие сталкиваются с лавинообразным падением производительности при выводе деревьев значений на форму. Казалось бы, скромная выборка из 6 000 строк в дереве может загружаться долгие минуты, полностью блокируя интерфейс пользователя.

В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем внутренние механизмы платформы и рассмотрим самые эффективные способы оптимизации, включая технологию «ленивой загрузки» (Lazy Loading), оптимизацию типов данных и отказ от избыточных вызовов серверного контекста.

Выясняем причину: почему тормозит ЗначениеВРеквизитФормы?

Давайте проанализируем классический код, который часто пишут разработчики и который приводит к сильным зависаниям интерфейса:


&НаСервере
Процедура ЗаполнитьДеревоНаСервере()
    РезультатЗапроса = Запрос.Выполнить();
    Дерево = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
    ЭтаФорма.ЗначениеВРеквизитФормы(Дерево, "Дерево");
КонецПроцедуры

На первый взгляд, все сделано по стандартам. Но именно в строке вызова метода ЗначениеВРеквизитФормы() кроется главная проблема. Давайте разберем по шагам, что происходит «под капотом» платформы при выполнении этого метода:

  1. Сериализация данных. Прикладной объект ДеревоЗначений, находящийся в оперативной памяти сервера, не может быть напрямую передан на клиент. Платформа должна преобразовать его в специальный внутренний XML/JSON формат (структуру ДанныеФормыДерево).
  2. Объем передаваемых данных. Если дерево содержит 6 000 строк, и у каждой строки есть несколько колонок, то общее количество элементов XML-структуры возрастает в геометрической прогрессии. Сериализуется не просто таблица, а сложнейший граф связей со всеми уровнями вложенности.
  3. Тяжелые типы данных. Ситуация усугубляется, если в колонках дерева хранятся сложные типы: ссылки на тяжелые справочники, документы или составные типы данных. Платформа тратит колоссальные ресурсы процессора на подготовку текстовых представлений для каждой ссылки, чтобы отобразить их на клиенте.
  4. Рендеринг интерфейса. Получив огромный массив сериализованных данных на клиенте, операционная система и тонкий клиент 1С начинают отрисовку элементов управления дерева, что вызывает перегрузку UI-потока.

Для оптимизации этого процесса нам необходимо кардинально изменить подход к заполнению и передаче данных. Рассмотрим пошагово ключевые методики ускорения работы.

Способ 1. Реализация динамической («ленивой») загрузки (Lazy Loading)

Это самый мощный и рекомендуемый способ оптимизации иерархических структур. Его суть заключается в том, чтобы не загружать на клиент всю базу сразу. Мы заполняем только самый верхний (первый) уровень дерева. Если у элемента есть подчиненные элементы, мы создаем для него одну пустую строку-маркер («плюсик»). Когда пользователь нажимает на этот «плюсик», система перехватывает событие и динамически догружает подчиненные строки только для этого узла.

Рассмотрим детальную реализацию этого алгоритма на примере иерархического справочника номенклатуры.

Шаг 1. Первоначальное заполнение корня дерева при открытии формы.

При инициализации формы мы делаем быстрый запрос, который выбирает только элементы верхнего уровня (у которых родитель пустой).


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    ИнициализироватьПервыйУровеньДерева();
КонецПроцедуры

&НаСервере
Процедура ИнициализироватьПервыйУровеньДерева()
    
    // Запрос выбирает только элементы верхнего уровня
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Номенклатура.Ссылка КАК Ссылка,
        |	Номенклатура.Наименование КАК Наименование,
        |	Номенклатура.ЭтоГруппа КАК ЭтоГруппа
        |ИЗ
        |	Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |	Номенклатура.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)";
    
    Результат = Запрос.Выполнить();
    Выборка = Результат.Выбрать();
    
    // Получаем коллекцию элементов дерева напрямую, без использования ЗначениеВРеквизитФормы
    КоллекцияЭлементов = РеквизитФормыВЗначение("Дерево").Строки;
    КоллекцияЭлементов.Очистить();
    
    Пока Выборка.Следующий() Цикл
        НоваяСтрока = КоллекцияЭлементов.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
        
        // Если это группа, добавляем пустую строку-маркер
        Если Выборка.ЭтоГруппа Тогда
            СтрокаМаркер = НоваяСтрока.Строки.Добавить();
            СтрокаМаркер.Наименование = "Загрузка..."; // Служебный маркер
        КонецЕсли;
    КонецЦикла;
    
    // Возвращаем объект обратно в реквизит формы
    ЗначениеВРеквизитФормы(КоллекцияЭлементов.Владелец(), "Дерево");
    
КонецПроцедуры

Шаг 2. Перехват раскрытия ветки на клиенте.

Для таблицы на форме определяем событие ПередРазворачиванием. Здесь мы проверим, загружены ли данные, или там все еще находится наш маркер «Загрузка...».


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

Шаг 3. Динамическая подгрузка ветки на сервере.

Обратите внимание: мы не конвертируем все дерево через ЗначениеВРеквизитФормы(). Мы точечно находим элемент формы на сервере через коллекцию ДанныеФормыДерево, удаляем маркер и добавляем новые строки. Это работает мгновенно!


&НаСервере
Процедура ПодгрузитьПодчиненныеСтрокиНаСервере(СтрокаИД)
    
    // Находим строку дерева непосредственно в реквизите формы по ее идентификатору
    РодительскийЭлемент = Дерево.НайтиПоИдентификатору(СтрокаИД);
    КоллекцияПодчиненных = РодительскийЭлемент.ПолучитьЭлементы();
    
    // Удаляем строку-маркер "Загрузка..."
    КоллекцияПодчиненных.Очистить();
    
    // Получаем реальные данные из БД только для этого родителя
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Номенклатура.Ссылка КАК Ссылка,
        |	Номенклатура.Наименование КАК Наименование,
        |	Номенклатура.ЭтоГруппа КАК ЭтоГруппа
        |ИЗ
        |	Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |	Номенклатура.Родитель = &Родитель";
    
    Запрос.УстановитьПараметр("Родитель", РодительскийЭлемент.Ссылка);
    Выборка = Запрос.Выполнить().Выбрать();
    
    Пока Выборка.Следующий() Цикл
        НоваяСтрока = КоллекцияПодчиненных.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
        
        // Если внутри новой строки тоже есть группы, вешаем маркер для дальнейшего раскрытия
        Если Выборка.ЭтоГруппа Тогда
            СтрокаМаркер = НоваяСтрока.ПолучитьЭлементы().Добавить();
            СтрокаМаркер.Наименование = "Загрузка...";
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

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

Способ 2. Оптимизация типов колонок дерева (Упрощение данных)

Если специфика вашей задачи требует вывода всего объема данных сразу, проанализируем структуру колонок самого дерева. Главное правило оптимизации в управляемых формах: чем проще типы данных в колонках, тем быстрее выполняется сериализация.

Давайте разберем практические шаги по оптимизации колонок:

Способ 3. Заполнение коллекции через специализированные функции БСП

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

В частности, метод ОбщегоНазначения.ЗаполнитьКоллекциюЭлементовДереваДанныхФормы() выполняет перенос данных из объекта ДеревоЗначений в реквизит формы типа ДанныеФормыДерево по максимально оптимизированным внутренним алгоритмам платформы, минимизируя накладные расходы.

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


&НаСервере
Процедура ЗаполнитьЧерезБСП(ДеревоЗначенийИсточник)
    // Быстрое заполнение реквизита "Дерево" на основе подготовленного дерева значений
    ОбщегоНазначения.ЗаполнитьКоллекциюЭлементовДереваДанныхФормы(ДеревоЗначенийИсточник, Дерево);
КонецПроцедуры

Способ 4. Использование итеративного обхода через стек вместо рекурсии

Часто перед заполнением или выводом дерева разработчики выполняют его предварительную обработку на сервере (например, рассчитывают итоги, устанавливают картинки состояния, проставляют флаги). Традиционно для этого используется рекурсивный вызов функций. Однако на больших деревьях рекурсия создает огромную нагрузку на стек вызовов платформы и снижает общую скорость обработки.

Рекомендуем заменить рекурсию итеративным обходом с использованием массива в качестве стека. Посмотрим на этот пример:


&НаСервере
Процедура ОбработатьДеревоИтеративно(ДеревоЗначений)
    
    // Создаем массив, который будет выступать в роли стека
    СтекСтрок = Новый Массив;
    
    // Помещаем в стек все корневые строки дерева
    Для Каждого КорневаяСтрока Из ДеревоЗначений.Строки Цикл
        СтекСтрок.Добавить(КорневаяСтрока);
    КонецЦикла;
    
    // Обрабатываем элементы, пока стек не опустеет
    Пока СтекСтрок.Количество() > 0 Цикл
        
        // Извлекаем последний элемент из стека (LIFO)
        ТекущийИндекс = СтекСтрок.Количество() - 1;
        ТекущаяСтрока = СтекСтрок[ТекущийИндекс];
        СтекСтрок.Удалить(ТекущийИндекс);
        
        // Выполняем полезную обработку строки
        ТекущаяСтрока.Наименование = ВРег(ТекущаяСтрока.Наименование); 
        
        // Помещаем подчиненные строки текущего элемента в стек для дальнейшей обработки
        Для Каждого ДочерняяСтрока Из ТекущаяСтрока.Строки Цикл
            СтекСтрок.Добавить(ДочерняяСтрока);
        КонецЦикла;
        
    КонецЦикла;
    
КонецПроцедуры

Этот подход гарантирует стабильность приложения, исключает риски переполнения стека и работает на 30–50% быстрее классического рекурсивного обхода дерева.

Способ 5. Архитектурное разделение: вычисления отдельно, визуализация отдельно

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

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

  1. Все промежуточные расчеты, группировки и тяжелые выборки делайте на стороне сервера в объектах типа ТаблицаЗначений, Соответствие или во временных таблицах СУБД.
  2. Для связи таблиц и быстрого поиска используйте индексацию колонок на сервере.
  3. На управляемую форму передавайте исключительно финальный, очищенный от лишних служебных данных результат, предназначенный только для чтения пользователем.

Соблюдение этих правил позволит вам создавать отзывчивые и быстрые интерфейсы даже при работе с очень большими объемами иерархических данных.

← На главную