&НаСервере или &НаСервереБезКонтекста: как правильно выбрать директиву и оптимизировать клиент-серверные вызовы в 1С

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

В разработке на платформе 1С:Предприятие 8.3 выбор правильной директивы компиляции для серверных процедур и функций является базовым, но в то же время критически важным аспектом оптимизации производительности. Ошибки при проектировании клиент-серверного взаимодействия приводят к избыточному сетевому трафику, зависанию интерфейса и нерациональному использованию ресурсов сервера приложений. Разберем детально, как работают директивы &НаСервере и &НаСервереБезКонтекста, проанализируем неочевидные нюансы платформы и выработаем надежные правила их применения.

Базовые понятия: Контекстный и Внеконтекстный вызовы

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

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

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

Сценарий 1: Простые параметры (Организация, Контрагент)

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

Выясним причину, почему в данном случае использование &НаСервереБезКонтекста является единственно верным архитектурным решением:

  1. Объем передаваемых данных: При контекстном вызове на сервер улетит весь контекст, включая тяжелую таблицу значений (представленную типом ДанныеФормыКоллекция). Даже если вы не трогаете ее в коде, платформа все равно сериализует ее туда и обратно.
  2. Инициализация окружения: Для выполнения внеконтекстного метода серверу не требуется тратить процессорное время на воссоздание структуры формы в памяти. Метод выполняется мгновенно, аналогично вызову функции из общего модуля.

Рассмотрим, как правильно реализовать этот алгоритм на практике.

Неправильный вариант (избыточный контекстный вызов):


&НаКлиенте
Процедура ПолучитьОстаткиКонтекстно(Команда)
    ПолучитьОстаткиНаСервере(); // контекст формы уходит на сервер целиком
КонецПроцедуры

&НаСервере
Процедура ПолучитьОстаткиНаСервере()
    // Реквизиты Организация и Контрагент доступны напрямую,
    // но за это мы заплатили передачей всей таблицы формы
    Запрос = Новый Запрос;
    Запрос.Текст = "..."; // Текст запроса с использованием параметров Организация и Контрагент
    // ... обработка запроса
КонецПроцедуры

Правильный вариант (быстрый внеконтекстный вызов):


&НаКлиенте
Процедура ПолучитьОстаткиВнеКонтекста(Команда)
    // Передаем только нужные параметры
    Остатки = ПолучитьОстаткиНаСервереБезКонтекста(Объект.Организация, Объект.Контрагент);
    // Обрабатываем результат на клиенте
КонецПроцедуры

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

Сценарий 2: Оптимизация работы с таблицами (ДанныеФормыКоллекция)

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

Разберем, почему ручная сериализация таблиц вредит производительности:

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

Сценарий 3: Вызовы «Сервер -> Сервер» и вложенность процедур

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

Проанализируем ситуацию по шагам:

  1. Отсутствие повторного сетевого обмена: Если клиент вызвал контекстную процедуру &НаСервере, контекст формы уже прилетел на сервер и развернулся в оперативной памяти процесса rphost. Если из этой контекстной процедуры вызывается другая процедура формы (неважно, с директивой &НаСервере или &НаСервереБезКонтекста), то никакого повторного обмена с клиентом или сериализации данных не происходит. Вызов выполняется локально внутри одного серверного процесса.
  2. Правила вызова методов:
    • Из контекстного метода (&НаСервере) можно вызывать внеконтекстный метод (&НаСервереБезКонтекста). Однако передавать туда реквизиты формы в качестве параметров считается плохим тоном разработки (антипаттерном), ведь внеконтекстный метод проектируется как независимая бизнес-логика.
    • Из внеконтекстного метода (&НаСервереБезКонтекста) вызвать контекстный метод (&НаСервере) невозможно. Попытка такого вызова приведет к ошибке компиляции модуля формы. Это логично: во внеконтекстном методе на сервере физически отсутствует объект контекста формы, и вызвать метод, который требует этот контекст, платформа не может.

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


&НаСервере
Процедура КонтекстныйМетод1()
    // Допустимо. Мы на сервере, контекст уже здесь.
    КонтекстныйМетод2(); 
    
    // Допустимо. Вызываем внеконтекстный метод.
    ВнеконтекстныйМетод(Объект.Организация); 
КонецПроцедуры

&НаСервере
Процедура КонтекстныйМетод2()
    // Какая-то работа с реквизитами формы напрямую
    Объект.Комментарий = "Изменено на сервере";
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ВнеконтекстныйМетод(Знач ТекущаяОрганизация)
    // ОШИБКА КОМПИЛЯЦИИ! Отсюда нельзя вызвать КонтекстныйМетод2(),
    // так как в методе ВнеконтекстныйМетод нет контекста формы.
    // КонтекстныйМетод2(); 
КонецПроцедуры

Сценарий 4: Серверные вызовы в циклах

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

Однако здесь кроется более серьезная архитектурная проблема. Делать любые серверные вызовы (даже внеконтекстные) внутри циклов на клиенте категорически запрещено стандартами разработки 1С.

Каждое обращение к серверу — это отправка сетевого пакета (roundtrip). Время сетевой задержки (ping) в реальных распределенных сетях или облачных инфраструктурах может составлять от 2 до 50 миллисекунд. Если вы выполните 10 000 серверных вызовов в цикле, то даже при моментальной работе самого сервера пользователь будет ждать завершения операции несколько минут из-за сетевых задержек.

Проанализируем правильный вариант оптимизации кода из сообщения 10. Вместо совершения множества мелких вызовов с клиента на сервер, необходимо перенести весь цикл целиком на сервер.

Как делать НЕЛЬЗЯ (даже если вызовы внеконтекстные):


&НаКлиенте
Процедура ПлохойЦикл(Команда)
    Для Й = 1 По 10000 Цикл
        // 10 000 сетевых вызовов убьют производительность системы в реальной сети!
        Результат = ОбработкаБезКонтекста(Число1, Число2); 
    КонецЦикла;
КонецПроцедуры

Как делать ПРАВИЛЬНО (один сетевой вызов):


&НаКлиенте
Процедура ОптимальныйЦикл(Команда)
    // Делаем ровно ОДИН сетевой вызов, передавая параметры для массовой обработки
    РезультатССервера = ВыполнитьМассовыйРасчетНаСервере(Число1, Число2, 10000);
КонецПроцедуры

&НаСервереБезКонтекста
Функция ВыполнитьМассовыйРасчетНаСервере(Знач Параметр1, Знач Параметр2, Знач КоличествоИтераций)
    // Весь цикл крутится в оперативной памяти сервера приложений без сетевых задержек
    Сумма = 0;
    Для Й = 1 По КоличествоИтераций Цикл
        Сумма = Сумма + (Параметр1 + Параметр2);
    КонецЦикла;
    Возврат Сумма;
КонецФункции

Сценарий 5: Критические ограничения на контекстные вызовы

Мало кто из начинающих разработчиков знает, что существуют сценарии, в которых использование контекстных вызовов (&НаСервере) не просто нежелательно, а технически опасно и запрещено стандартами разработки 1С.

Речь идет о клиентских обработчиках транзакционных событий формы, таких как:

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

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

Резюме и чеклист для разработчика

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

  1. Используйте &НаСервереБезКонтекста во всех случаях, когда вам нужно получить справочную информацию, выполнить расчет или сделать запрос к базе данных, результат которого можно вернуть в виде простых типов (число, строка, булево) или массива/структуры.
  2. Используйте &НаСервере только тогда, когда вам необходимо напрямую изменить состояние элементов формы на сервере, прочитать или записать основной реквизит формы (тип ДокументОбъект, СправочникОбъект) через вызов РеквизитФормыВЗначение.
  3. Никогда не выполняйте ручную сериализацию таблиц формы (перегонку ДанныеФормыКоллекция в массивы структур на клиенте) ради того, чтобы вызвать внеконтекстный метод. Встроенные оптимизаторы платформы сделают синхронизацию таблиц в контекстном вызове быстрее и надежнее.
  4. Ни в коем случае не размещайте серверные вызовы (с любыми директивами) внутри клиентских циклов. Всегда переносите циклы на сторону сервера приложений.
← На главную