Как оптимизировать ПриПолученииДанных и устранить зависания обычных форм 1С при прокрутке?

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

Разработчики, работающие с обычными формами на платформе 1С:Предприятие 8.3, часто сталкиваются с проблемой производительности динамических списков (таких как СправочникСписок или ДокументСписок). Типичный сценарий: в список номенклатуры необходимо вывести дополнительные динамические данные — актуальные цены, остатки по складам или специфические свойства. Самым очевидным местом для этого кажется событие ПриПолученииДанных табличного поля.

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

Почему идея «выбрать строки выше и ниже» не работает?

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

  1. Сложная динамическая сортировка: Пользователь может в любой момент кликнуть по заголовку любой колонки списка, изменив порядок строк. Чтобы понять, какие элементы идут «до» и «после» текущего, нам придется полностью воссоздать структуру сортировки табличного поля в запросе.
  2. Динамические отборы и фильтры: На список могут быть наложены сложные пользовательские или программные отборы. Запрос предвыборки должен учитывать все эти условия в реальном времени.
  3. Режим иерархического просмотра: Если в списке включен иерархический режим, строки выводятся с учетом групп и элементов. Пытаться вычислить в запросе «следующий» элемент с учетом раскрытых групп — нетривиальная задача, требующая огромных ресурсов.

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

Решение 1. Отложенное оформление строк через обработчик ожидания (Debouncing)

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

Давайте реализуем этот алгоритм по шагам.

Во-первых, объявим в модуле формы глобальные переменные для хранения кэша данных и списка объектов, ожидающих оформления:


Перем мКэшДанных; // Соответствие: Ключ - СсылкаНоменклатуры, Значение - Структура с ценами и остатками
Перем мБуферОформления; // Массив ссылок, которые необходимо рассчитать

Во-вторых, инициализируем эти переменные при открытии формы:


Процедура ПриОткрытии()
    мКэшДанных = Новый Соответствие;
    мБуферОформления = Новый Массив;
КонецПроцедуры

В-третьих, напишем обработчик события ПриПолученииДанных. Здесь мы будем быстро выводить данные, если они уже есть в кэше. Если данных в кэше нет, мы поместим ссылку в буфер и перезапустим обработчик ожидания:


Процедура СправочникСписокПриПолученииДанных(Элемент, ОформленияСтрок)
    
    МассивДляЗапроса = Новый Массив;
    
    Для Каждого ОформлениеСтроки Из ОформленияСтрок Цикл
        ДанныеСтроки = ОформлениеСтроки.ДанныеСтроки;
        Если ДанныеСтроки = Неопределено Тогда
            Продолжить;
        КонецЕсли;
        
        СсылкаНоменклатуры = ДанныеСтроки.Ссылка;
        
        // Пытаемся получить данные из нашего локального кэша
        ДанныеИзКэша = мКэшДанных.Получить(СсылкаНоменклатуры);
        
        Если ДанныеИзКэша <> Неопределено Тогда
            // Данные в кэше есть — выводим их мгновенно без обращений к БД
            ОформлениеСтроки.Ячейки.Остаток.Значение = ДанныеИзКэша.Остаток;
            ОформлениеСтроки.Ячейки.Цена.Значение = ДанныеИзКэша.Цена;
        Иначе
            // Данных в кэше нет. Показываем временную заглушку
            ОформлениеСтроки.Ячейки.Остаток.Значение = "...";
            ОформлениеСтроки.Ячейки.Цена.Значение = "...";
            
            // Добавляем ссылку в массив для последующего запроса
            Если МассивДляЗапроса.Найти(СсылкаНоменклатуры) = Неопределено Тогда
                МассивДляЗапроса.Добавить(СсылкаНоменклатуры);
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
    // Если появились новые элементы для расчета, сбрасываем и взводим таймер
    Если МассивДляЗапроса.Количество() > 0 Тогда
        мБуферОформления = МассивДляЗапроса;
        
        // Сбрасываем таймер: если пользователь продолжает крутить колесико, 
        // обработчик не успеет сработать, обеспечивая плавность
        ОтключитьОбработчикОжидания("ВыполнитьОтложенноеОформление");
        ПодключитьОбработчикОжидания("ВыполнитьОтложенноеОформление", 0.3, Истина);
    КонецЕсли;
    
КонецПроцедуры

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


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

Такой подход полностью устраняет лаги. При быстрой прокрутке пользователь видит заглушки «...», список прокручивается идеально плавно, а данные появляются в ячейках через 0.3 секунды после остановки скроллинга.

Решение 2. Ленивое кэширование на клиенте (Lazy Caching)

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

Проанализируем преимущества ленивого кэша:

Решение 3. Вынос тяжелых данных в отдельную панель (UI-оптимизация)

Зачастую разработчики перегружают интерфейс, пытаясь вывести абсолютно всю сопутствующую информацию прямо в строки динамического списка. Но действительно ли менеджеру по продажам нужно видеть остатки на 4 складах и 5 видов цен одновременно для 30 позиций на экране?

Гораздо более эргономичное и быстродействующее решение — показывать эту информацию только для **одной текущей (активной) строки** в отдельной панели формы. Разберем этот вариант по шагам:

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

Рассмотрим пример реализации:


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

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

Этот способ кардинально повышает юзабилити системы. Пользователь видит подробные остатки и цены по конкретному товару, на котором сфокусирован его взгляд, а сам поиск и прокрутка каталога работают мгновенно.

Решение 4. Оптимизация на уровне СУБД через регистр сведений

Если бизнес-требования жестко настаивают на отображении остатков и цен именно внутри колонок списка, а запросы к виртуальным таблицам остатков и срезов последних выполняются слишком долго, необходимо оптимизировать архитектуру хранения данных в СУБД.

Соединение основной таблицы справочника с виртуальными таблицами остатков и цен (да еще и по нескольким складам и типам цен) создает колоссальную нагрузку на SQL-сервер. Чтобы обойти это ограничение, выполните следующие шаги:

  1. Создайте специальный физический регистр сведений, например, ОстаткиИЦеныДляПодбора (измерения: Номенклатура, Склад, ТипЦены; ресурсы: Остаток, Цена).
  2. Настройте быстрое обновление этого регистра при проведении документов движения товаров и изменении цен (или делайте периодический пересчет регламентным заданием раз в несколько минут, если мгновенная актуальность не критична).
  3. В событии ПриПолученииДанных делайте простой и быстрый запрос к физической таблице этого регистра без использования тяжелых виртуальных таблиц ОстаткиИОбороты или СрезПоследних. Запрос к индексированной физической таблице по массиву ссылок выполняется СУБД практически мгновенно.

Сравнительный анализ методов

Проанализируем сильные и слабые стороны каждого подхода, чтобы выбрать наиболее подходящий под ваши задачи:

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

← На главную