Почему тормозит переключение строк в большом дереве значений на управляемой форме и как это исправить?

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

При разработке сложных аналитических рабочих мест и обработок в среде управляемого интерфейса 1С разработчики нередко сталкиваются с ситуацией, когда вывод разветвленного дерева значений приводит к ощутимым задержкам интерфейса. Пользователь нажимает клавишу стрелки вниз или кликает мышью по узлу дерева, и платформа замирает на 3–4 секунды. Аналогичные зависания могут проявляться и при обычном переключении между закладками формы. Разберем по шагам, из-за чего возникает эта проблема и как оптимизировать работу с большими иерархическими структурами.

Выясним причину: почему дерево значений теряет отзывчивость

Для обнаружения узкого места в первую очередь запустим замер производительности в конфигураторе. На практике почти всегда выясняется, что само дерево отрисовывается быстро, а задержки возникают в обработчике события ПриАктивизацииСтроки. Проанализируем архитектурные факторы, которые приводят к деградации скорости:

  1. Сериализация контекста формы при вызовах сервера. Данные дерева на клиенте хранятся в специальном объекте ДанныеФормыДерево. Если при каждой смене строки в обработчике вызывается серверная процедура с директивой &НаСервере, платформа выполняет контекстный вызов. В этот момент весь объем данных формы (тысячи строк с множеством колонок) сериализуется и передается на сервер, а затем обратно. Даже в толстом клиенте обычного приложения внутренняя сериализация контекста вызывает секундные лаги.
  2. Особенности события «ПриАктивизацииСтроки». Платформа генерирует данное событие не только при физическом переходе курсора на новую позицию, но и при смене страниц панели, получении фокуса таблицей или программном обновлении элементов. В результате тяжелый код может выполняться по несколько раз вхолостую.
  3. Линейный поиск в коллекциях формы на клиенте. Реквизиты формы на стороне клиента являются коллекциями ДанныеФормыКоллекция. У них отсутствует объект Индексы, свойственный серверной ТаблицаЗначений. Использование ручных переборов или метода НайтиСтроки по большим связанным таблицам на клиенте выполняет последовательное сканирование строк со сложностью O(N).

Способ 1. Отсечение холостых срабатываний обработчика

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

Посмотрим на пример реализации в модуле формы:


&НаКлиенте
Переменная ПредыдущаяСтрокаДерева;

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

Способ 2. Использование штатного свойства «ОтборСтрок» для подчиненных таблиц

Часто в обработчике ПриАктивизацииСтроки пытаются вручную отфильтровать соседнюю таблицу через обход строк или метод НайтиСтроки. Однако платформа предоставляет встроенный интерфейсный механизм клиентской фильтрации через свойство элемента управления таблицы ОтборСтрок.

Разберем пример, в котором подчиненная таблица привязывается к ключу выбранного узла дерева без единого серверного вызова и без линейного перебора коллекции в цикле:


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

Способ 3. Переход на бесконтекстные серверные вызовы (&НаСервереБезКонтекста)

Если для получения подчиненных данных серверный запрос все же строго необходим, категорически исключим контекстные процедуры &НаСервере. Объявим метод с директивой &НаСервереБезКонтекста и передадим в него только минимально необходимые параметры (ссылку на элемент, идентификатор узла), исключив передачу структуры всей формы.

Посмотрим на корректную схему вызова:


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

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

Способ 4. Реализация задержки через обработку ожидания

Когда пользователь быстро листает дерево стрелками на клавиатуре, событие ПриАктивизацииСтроки вызывается для каждой промежуточной позиции. В результате система ставит в очередь десятки ресурсоемких операций. Для решения этой проблемы применим паттерн отложенного выполнения: сбросим и перезапустим короткий таймер через метод ПодключитьОбработчикОжидания.

Рассмотрим, как это реализовать в клиентском коде:


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

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

Способ 5. Кеширование данных на клиенте с помощью объекта «Соответствие»

Если вся информация для детализации может быть подготовлена при первичном открытии формы или нажатии кнопки заполнения, исключим постоянные поиски в коллекциях формы. Заполним на сервере структуру данных и передадим ее на клиент в объект Соответствие, где ключом будет выступать идентификатор строки или ссылка, а значением — массив или структура подчиненных строк.

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

← На главную