Разработка интерфейсов в системе 1С:Предприятие часто сопровождается созданием различных динамических списков, журналов документов и справочников. На начальном этапе работы начинающие программисты часто сталкиваются с проблемой: при прокрутке списка (скроллинге) интерфейс программы начинает сильно «зависать» и тормозить. Пользователи жалуются на низкую скорость работы, а загрузка процессора клиентом 1С возрастает до максимума.
Выясним причину такого поведения системы. Разберем по шагам классические ошибки при оформлении строк, проанализируем примеры неоптимального кода и рассмотрим профессиональные методы пакетной оптимизации вывода данных как для обычных, так и для управляемых форм.
Основным источником проблем с производительностью в обычных формах 1С является использование события ПриВыводеСтроки для выполнения сложных расчетов или получения связанных данных из базы.
Давайте проанализируем ситуацию. Данное событие вызывается платформой 1С **индивидуально для каждой строки**, которая в данный момент отрисовывается на экране монитора. Если на экране пользователя отображается 30 строк, то при открытии формы обработчик выполнится ровно 30 раз. При прокрутке списка на каждую новую строку событие будет срабатывать снова и снова.
Рассмотрим фрагмент кода, который часто пишут новички:
Для Каждого СтрокаВагоны Из ОформлениеСтроки.ДанныеСтроки.Ссылка.Вагоны Цикл
Если СтрокаВагоны.ФактическаяСтепеньНаполненияКотла > СтрокаВагоны.ПредельнаяСтепеньНаполненияКотла Тогда
ОформлениеСтроки.Ячейки.Номер.ЦветФона = WebЦвета.Красный;
КонецЕсли;
КонецЦикла;
Что происходит в этот момент на уровне платформы? Обращение через точку к свойству Ссылка (например, ОформлениеСтроки.ДанныеСтроки.Ссылка.Вагоны) заставляет систему совершить полноценный и крайне «тяжелый» запрос к СУБД, чтобы получить весь объект документа со всеми его табличными частями. Если в списке отображается 30 строк, система выполнит 30 отдельных сетевых запросов к базе данных. В результате возникает классическая проблема N+1 запроса, которая мгновенно парализует работу интерфейса при быстром скроллинге.
Для решения этой проблемы разработчики переводят логику в событие таблицы ПриПолученииДанных. Это событие обрабатывает строки не по одной, а сразу **пакетом (порцией)** для всех видимых на экране элементов. Однако некорректная реализация этого механизма может свести на нет всю пользу от оптимизации.
Рассмотрим пример кода, который часто позиционируется как рабочий, но содержит в себе критические архитектурные ошибки:
Для каждого ТекСтрока из ОформленияСтрок Цикл
ПОКА Выборка.НайтиСледующий(Новый Структура("ТочкаОбслуживания", ТекСтрока.ДанныеСтроки.Ссылка)) ЦИКЛ
Если Выборка.РабочаяТочка Тогда
ТекСтрока.ЦветФона = ЦветДоставка;
КонецЕсли;
КонецЦикла;
Выборка.Сбросить();
КонецЦикла;
Проанализируем этот алгоритм подробнее и выявим две критические ошибки:
НайтиСледующий() и последующего Сбросить() внутри цикла обхода строк оформления приводит к тому, что для каждой видимой строки платформа вынуждена заново сканировать всю выборку СУБД с самого начала. Сложность такого алгоритма становится квадратичной: O(N * M), где N — количество строк на экране, а M — количество записей в выборке. При увеличении объема данных этот код будет зависать точно так же, как и построчный вывод.Выполнить() — это отдельный раунд коммуникации между клиентом, сервером приложений и СУБД.Для того чтобы оптимизировать вывод данных и добиться мгновенного отклика интерфейса, мы должны применить классический паттерн быстрого поиска данных через объект Соответствие (ассоциативный массив) и объединить все запросы к БД в один пакетный запрос.
Разберем этот подход по шагам:
Соответствие, где ключом будет выступать ссылка на объект, а значением — структура с его свойствами. Получение данных из Соответствие в 1С выполняется за константное время O(1), что гарантирует максимальную скорость работы.Посмотрим на пример правильной и быстрой реализации обработчика:
Процедура СписокПриПолученииДанных(Элемент, ОформленияСтрок)
// Шаг 1. Собираем массив уникальных ссылок видимых строк
МассивСсылок = Новый Массив;
Для Каждого СтрокаОформления Из ОформленияСтрок Цикл
Если НЕ СтрокаОформления.ДанныеСтроки.ЭтоГруппа Тогда
МассивСсылок.Добавить(СтрокаОформления.ДанныеСтроки.Ссылка);
КонецЕсли;
КонецЦикла;
Если МассивСсылок.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Шаг 2. Создаем один пакетный запрос для получения всех данных за раз
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Состояния.ТочкаОбслуживания КАК ТочкаОбслуживания,
| Состояния.РабочаяТочка КАК РабочаяТочка
|ИЗ
| РегистрСведений.СостояниеТочекОбслуживания.СрезПоследних(&ТекущаяДата, ТочкаОбслуживания В (&МассивСсылок)) КАК Состояния
|;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| КИ.Объект КАК Объект,
| КИ.Представление КАК Телефон
|ИЗ
| РегистрСведений.КонтактнаяИнформация КАК КИ
|ГДЕ
| КИ.Объект В(&МассивСсылок)
| И КИ.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.Телефон)";
Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДата());
// Шаг 3. Выполняем пакет запросов и заполняем Соответствия
РезультатыПакета = Запрос.ВыполнитьПакет();
// Соответствие для состояний точек обслуживания
СостоянияТочек = Новый Соответствие;
ВыборкаСостояний = РезультатыПакета[0].Выбрать();
Пока ВыборкаСостояний.Следующий() Цикл
СостоянияТочек.Вставить(ВыборкаСостояний.ТочкаОбслуживания, ВыборкаСостояний.РабочаяТочка);
КонецЦикла;
// Соответствие для телефонов
ТелефоныТочек = Новый Соответствие;
ВыборкаТелефонов = РезультатыПакета[1].Выбрать();
Пока ВыборкаТелефонов.Следующий() Цикл
ТелефоныТочек.Вставить(ВыборкаТелефонов.Объект, ВыборкаТелефонов.Телефон);
КонецЦикла;
// Шаг 4. Оформляем строки, используя быстрое получение данных из кэша
Для Каждого СтрокаОформления Из ОформленияСтрок Цикл
СсылкаНаОбъект = СтрокаОформления.ДанныеСтроки.Ссылка;
// Быстрое раскрашивание строк по состоянию рабочей точки
РабочаяТочка = СостоянияТочек[СсылкаНаОбъект];
Если РабочаяТочка <> Неопределено Тогда
Если РабочаяТочка Тогда
СтрокаОформления.ЦветФона = Новый Цвет(230, 255, 235); // ЦветДоставка
Иначе
СтрокаОформления.ЦветФона = Новый Цвет(230, 250, 250); // ЦветИнкассация
КонецЕсли;
КонецЕсли;
// Заполнение данных телефона в ячейку
Телефон = ТелефоныТочек[СсылкаНаОбъект];
Если Телефон <> Неопределено Тогда
СтрокаОформления.Ячейки.Телефон.Значение = Телефон;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Рассмотренный выше метод значительно ускорит работу вашей системы. Однако для высоконагруженных списков с тысячами записей этого может быть недостаточно. Рассмотрим три важных приема глубокой оптимизации списков, о которых редко упоминают в базовых курсах разработки.
1. Проверка видимости колонок на клиенте. Перед тем как включить сбор информации по адресам или телефонам в массив, обязательно проанализируйте, отображаются ли соответствующие колонки на экране в данный момент. Если пользователь скрыл колонку с телефоном через настройки списка, то делать запрос в базу данных для получения телефона — это бесполезная трата системных ресурсов. Всегда ставьте условие:
Если Элемент.Колонки.Телефон.Видимость Тогда
// Только в этом случае собираем ссылки и делаем запрос для телефонов...
КонецЕсли;
2. Кэширование данных на уровне модуля формы. При активном скроллинге вверх и вниз платформа 1С повторно вызывает событие ПриПолученииДанных для одних и тех же строк. Чтобы не дергать СУБД дублирующимися запросами, мы можем объявить переменную модуля формы мКэшДанных типа Соответствие. При получении новой порции строк мы сначала проверяем наличие данных в локальном кэше переменной, и отправляем запрос в базу данных только по тем ссылкам, которых еще нет в кэше.
3. Борьба с эффектом лавинообразного скроллинга (таймаут). При быстром поиске или непрерывном перелистывании списка «ползунком» события отрисовки вызываются непрерывно. Для борьбы со «стеком» вызовов можно использовать простой отложенный запуск обработки через подключение обработчика ожидания. Запуск тяжелого расчета откладывается на 0.2-0.3 секунды. Если пользователь продолжает быстро крутить список, таймер сбрасывается. Реальный запрос к СУБД произойдет только тогда, когда пользователь остановит скроллинг на конкретной странице.
Описанные выше события обычных форм считаются устаревшими и не используются в современных конфигурациях на управляемых формах. Концепция клиент-серверного разделения требует совершенно других подходов к оформлению данных.
Рассмотрим два современных инструмента оптимизации списков в управляемом приложении:
Для простых задач изменения шрифта, цвета текста, цвета фона или доступности ячеек (как в примере с вагонами) писать программный код вообще запрещено стандартами разработки. Все эти условия настраиваются декларативно в свойствах динамического списка через механизм Условного оформления.
Платформа 1С самостоятельно транслирует эти правила настройки в SQL-запрос к базе данных. СУБД возвращает на клиент уже размеченные и подготовленные стили отображения вместе с порцией данных, что работает в десятки раз быстрее любого программного кода.
Если для вывода строки требуются сложные алгоритмы, которые невозможно описать условиями динамического списка, мы должны использовать специализированное событие ПриПолученииДанныхНаСервере. Особенность этого обработчика заключается в следующем:
&НаСервереБезКонтекста. Это исключает передачу лишних данных формы по сети между клиентом и сервером.Применяя эти проверенные подходы на практике, вы гарантируете стабильную и быструю работу интерфейса ваших решений даже при работе с терабайтными базами данных и тысячами одновременно работающих пользователей.