Как определить интерактивное или программное изменение строки и оптимизировать перебор данных в 1С?

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

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

Поведение события ПриАктивизацииСтроки при программном изменении

Изначально у нас возник вопрос: срабатывает ли событие ПриАктивизацииСтроки при программной смене свойства ТекущаяСтрока табличной части формы? Опыт показал, что существует некоторое недопонимание этого механизма.

Мы выяснили, что для обычных таблиц значений (реквизитов формы типа ТаблицаЗначений, выведенных на форму как таблица формы) событие ПриАктивизацииСтроки не срабатывает при программной смене свойства ТекущаяСтрока. Это событие предназначено для реакции на интерактивные действия пользователя, такие как:

  1. Открытие формы.
  2. Прокрутка списка.
  3. Выбор группы (родителя).
  4. Нажатие кнопки "Обновить текущий список".
  5. Другие действия пользователя, приводящие к смене активной строки.

Если мы программно присвоим новое значение свойству ТекущаяСтрока, обработчик ПриАктивизацииСтроки не будет вызван. Это важное различие, которое необходимо учитывать при проектировании логики.

Для подтверждения этого поведения, мы всегда рекомендуем обращаться к официальной документации. В «Синтакс-помощнике», в разделе «Интерфейс (управляемый)» -> «Таблица формы» -> «ТаблицаФормы» -> «События» -> «ПриАктивизацииСтроки», в секции «Примечание» четко указано, что это событие предназначено для интерактивных действий. Если вам необходимо выполнять какие-либо действия при программной смене строки, то эту логику следует вызывать непосредственно после присвоения нового значения свойству ТекущаяСтрока, а не полагаться на событие ПриАктивизацииСтроки.

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

Выявление и устранение проблем производительности при переборе строк

После прояснения вопроса с ПриАктивизацииСтроки, мы столкнулись с более серьезной проблемой — низкой производительностью при программном переборе строк табличной части. Нам потребовалось в цикле перебрать 1500 строк «главной» таблицы, чтобы сравнить значение поля ВидЗанятости со значением Совместительство. Результаты этого эксперимента наглядно продемонстрировали критическую важность правильного подхода к клиент-серверному взаимодействию.

Мы проанализировали три различных подхода к решению этой задачи:

  1. Контекстные серверные вызовы в цикле

    Изначально мы использовали обычную серверную функцию, вызванную из клиентского кода в каждой итерации цикла. Такая функция помечается директивой &НаСервере и имеет доступ ко всему контексту формы.

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

    Пример (псевдокод):

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

    Результат: При переборе 1500 строк с таким подходом, цикл выполнялся от 131 секунды на релизе 27 до 150 секунд на релизе 24. Это неприемлемо медленно для любой интерактивной работы.

  2. Внеконтекстные серверные вызовы (`&НаСервереБезКонтекста`)

    Следующим шагом мы модифицировали серверную функцию, добавив к ней директиву БезКонтекста.

    Суть улучшения: Функция, помеченная как &НаСервереБезКонтекста, не имеет доступа к контексту формы и не требует его передачи. При вызове такой функции с клиента на сервер передаются только явно указанные параметры. Это значительно сокращает объем данных, передаваемых по сети, и время на «упаковку»/«распаковку» контекста.

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

    Пример (псевдокод):

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

    Результат: После применения БезКонтекста, время выполнения того же цикла сократилось до 7 секунд. Это значительное улучшение (более чем в 18 раз!), подчеркивающее важность внеконтекстных вызовов, когда контекст формы не требуется.

  3. Полностью клиентская обработка данных

    Наконец, мы перенесли всю логику сравнения на клиент, исключив любые серверные вызовы из цикла.

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

    Пример (псевдокод):

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

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

Эти результаты наглядно показывают колоссальную разницу в производительности: от 150 секунд до менее чем 1 секунды! Это подтверждает давно известное правило: избегайте контекстных вызовов сервера в цикле любой ценой. Даже внеконтекстные вызовы, хотя и значительно быстрее, все равно добавляют накладные расходы по сравнению с полностью клиентской обработкой.

Общие рекомендации по оптимизации клиент-серверного взаимодействия

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

  1. Минимизация вызовов сервера в цикле

    Это золотое правило оптимизации. Как мы убедились, каждый контекстный вызов сервера — это дорогая операция. Мы должны стремиться к тому, чтобы количество клиент-серверных взаимодействий было минимальным, особенно внутри циклов. Вместо многократных мелких вызовов, лучше один раз передать большой объем данных или выполнить сложную операцию на сервере.

  2. Использование внеконтекстных вызовов (`&НаСервереБезКонтекста`)

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

  3. Объединение серверных вызовов (пакетная обработка)

    Вместо того чтобы делать несколько серверных вызовов из клиентского кода, мы рекомендуем объединять их в один. Например, если нам нужно обработать несколько строк, соберите необходимые данные (например, ключи или целые строки в виде массива) на клиенте, передайте весь массив на сервер одним вызовом. На сервере обработайте все элементы массива и верните результат также в виде коллекции. Такой подход значительно сократит количество клиент-серверных взаимодействий.

    
    // На клиенте
    МассивДанныхДляОбработки = Новый Массив;
    Для Каждого СтрокаТЗ Из МояТаблицаЗначений Цикл
        МассивДанныхДляОбработки.Добавить(СтрокаТЗ.УникальныйИдентификатор); // Или СтрокаТЗ целиком
    КонецЦикла;
    
    РезультатСервернойОбработки = ОбработатьДанныеНаСервере(МассивДанныхДляОбработки);
    
    // На сервере
    &НаСервереБезКонтекста
    Функция ОбработатьДанныеНаСервере(МассивДанных)
        // Здесь выполняем сложную логику для всего массива
        // ...
        Возврат Результат;
    КонецФункции;
    
  4. Оптимизация запросов и использование индексов

    Для ускорения поиска и выборки данных из базы данных критически важно оптимизировать запросы и использовать индексы. Индексы значительно ускоряют запросы, особенно при работе с большими объемами данных. Мы должны избегать сложных запросов и при необходимости разбивать большие запросы на несколько меньших, чтобы повысить их эффективность.

  5. Кэширование данных

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

  6. Использование методов Найти() и НайтиСтроки() для таблиц значений

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

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

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

    
    // Пример использования НайтиСтроки на клиенте
    МассивНайденныхСтрок = МояТаблицаЗначений.НайтиСтроки(Новый Структура("ВидЗанятости", ПредопределенноеЗначение("Перечисление.ВидыЗанятости.Совместительство")));
    Для Каждого НайденнаяСтрока Из МассивНайденныхСтрок Цикл
        // Обрабатываем найденную строку
    КонецЦикла;
    
  7. Особенности передачи таблиц значений между клиентом и сервером

    Платформа 1С:Предприятие не поддерживает прямую передачу мутабельных значений (к которым относится ТаблицаЗначений) с клиента на сервер. Для передачи таблицы значений нам необходимо преобразовать её в структуру, массив или строку JSON на клиенте, а затем обратно в ТаблицуЗначений на сервере. В качестве альтернативы можно использовать временное хранилище для больших объемов данных.

    
    // На клиенте для передачи ТаблицыЗначений на сервер
    Функция ПолучитьТаблицуДляПередачиНаСервер(Таблица)
        Возврат Таблица.Выгрузить(); // Выгружаем в ТаблицуЗначений (которая по факту становится СериализуемойТаблицей)
    КонецФункции
    
    // Затем на сервере получаем:
    &НаСервереБезКонтекста
    Функция ОбработатьТаблицуНаСервере(СервернаяТаблица)
        // СервернаяТаблица будет доступна как ТаблицаЗначений
        // ...
    КонецФункции;
    
  8. Замер производительности

    Для выявления «узких мест» в приложении и анализа серверных вызовов мы активно используем инструмент «Замер производительности», доступный в конфигураторе 1С. Он позволяет оценить частоту и скорость выполнения участков кода, а также точно определить, где выполнялся код — на клиенте или на сервере. Это незаменимый инструмент для точечной оптимизации.

  9. Общие принципы разработки управляемых приложений

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

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

← На главную