Серверный вызов в управляемой форме 1С: что происходит между кликом и ответом

· 9 мин

Содержание

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

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

Что физически происходит при вызове

Когда пользователь нажимает кнопку, срабатывает клиентский обработчик. Если внутри есть вызов процедуры с директивой &НаСервере, платформа делает несколько шагов:

  1. Собирает параметры вызова и, для &НаСервере, данные формы.
  2. Сериализует их и отправляет на сервер по сети.
  3. Сервер поднимает контекст формы (или готовит вызов без контекста).
  4. Выполняется ваш серверный код.
  5. Результат и изменённые данные формы упаковываются и едут обратно.
  6. Клиент применяет изменения и перерисовывает форму.

Клиент всё это время ждёт. Пока серверный код не вернул управление, форма «замирает», хотя сервер может работать быстро.

Из чего складывается время одного вызова:

  • 🌐 Сеть. В локальной сети это единицы миллисекунд. По удалённому каналу или через веб-публикацию — десятки и сотни.
  • 📦 Сериализация. Чем больше данных формы и параметров, тем дольше упаковка и распаковка.
  • ⚙️ Подготовка контекста. Для вызова с контекстом формы сервер восстанавливает состояние формы.
  • 🧮 Сам код. Запросы, циклы, проведение документов.

Реальные цифры зависят от канала и нагрузки. Ориентир: на удалённом подключении один «пустой» серверный вызов заметно медленнее, чем в локальной сети, а если их сотня в цикле, пользователь увидит паузу в несколько секунд.

Важная деталь: между вызовами сервер не хранит локальные переменные вашего кода. Вы вернулись на клиент, значит всё, что не передали и не сохранили в реквизитах формы или параметрах сеанса, потеряно.

Состояние между вызовами

Раз сервер ничего не помнит, у данных есть три «дома»:

  • 🗂️ Реквизиты формы. Живут, пока открыта форма, и ездят с &НаСервере.
  • 🔑 Параметры сеанса. Хранятся на сервере на время сеанса пользователя. Подходят для текущего подразделения или настроек, но не для крупных наборов данных.
  • 🗄️ База данных. Единственное надёжное место для всего, что должно пережить закрытие формы.

Хранить большие массивы в реквизитах формы невыгодно: они поедут по сети при каждом вызове с контекстом.

Директивы компиляции: где что выполняется

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

ДирективаГде выполняетсяЕсть ли контекст формыКогда применять
&НаКлиентеКлиентДаРабота с интерфейсом, обработчики событий
&НаСервереСерверДа, форма передаётся целикомКогда нужно читать и менять реквизиты формы
&НаСервереБезКонтекстаСерверНетЗапросы и расчёты, которым форма не нужна
&НаКлиентеНаСервереБезКонтекстаОбе стороныНетНебольшие универсальные функции без обращений к форме

Самое дорогое — &НаСервере. Платформа пересылает состояние формы в обе стороны, даже если вы обратились к одному реквизиту. На тяжёлой форме с большими табличными частями это ощутимо.

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

Области видимости

  • Клиентская процедура формы может вызвать серверную процедуру той же формы.
  • Серверная процедура формы не может вызвать клиентскую: сервер не знает о клиенте.
  • Общий модуль доступен с клиента только если у него установлен флаг «Вызов сервера», а сам он серверный.
  • Клиентские общие модули нельзя вызвать с сервера.

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

Параметры и результат: что реально летит по сети

Внутри одной стороны параметры можно менять «по месту», но при пересечении границы работает другое правило. Данные копируются, и изменённые значения возвращаются обратно клиенту, если параметр не помечен словом Знач.

Это значит:

  • 🔁 Параметр без Знач при серверном вызове едет на сервер и возвращается обратно в клиентскую переменную. Лишний трафик, если вы его только читаете.
  • ➡️ Параметр со Знач едет только туда. Для входных данных это оптимально.
  • ⬅️ Результат функции возвращается всегда. Нужно вернуть несколько значений, используйте структуру или параметры без Знач.

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

Что нельзя: объекты, привязанные к серверной сессии, например Запрос, РезультатЗапроса, выборки. Такие значения не пересекают границу. Платформа выдаст ошибку о невозможности передачи значения.

Не передавайте на клиент больше, чем нужно для отображения. Таблица на 50 тысяч строк, возвращённая «на всякий случай», превращается в секунды ожидания и расход памяти клиента.

Пример: считаем остатки на сервере без контекста формы и возвращаем только итог.

&НаКлиенте
Процедура ПоказатьОстаток(Команда)
    Остаток = ПолучитьОстатокНаСервере(Товар, Склад);
    Сообщить("Остаток: " + Остаток);
КонецПроцедуры

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

Имена регистра в вашей конфигурации будут другими. Суть в том, что наружу выходит одно число, а не набор данных.

Как сократить число вызовов и их вес

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

Типичные антипаттерны

  • 🔄 Серверный вызов внутри цикла на клиенте. Цикл по 200 строкам даёт 200 обращений.
  • 📞 Несколько подряд вызовов, где результат первого нужен только для второго. Объедините на сервере.
  • 📋 Вызов на каждое изменение поля, хотя можно собрать все изменения и отправить пачкой.
  • 🧾 Передача всей формы через &НаСервере, когда достаточно двух параметров.

Как переписать цикл

Было: клиент идёт по строкам и для каждой спрашивает сервер о цене. Стало: клиент собирает ссылки на номенклатуру в массив, один раз отправляет на сервер, получает соответствие «номенклатура → цена» и расставляет цены локально.

На сервере в таком случае делается один запрос с условием В (&МассивНоменклатуры), а не сотня мелких.

Кеширование

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

Как понять, что вызовов слишком много:

  • В технологическом журнале серверные вызовы фиксируются отдельными событиями. Анализируйте их частоту в пределах одного действия.
  • В «Замере производительности» смотрите время клиентской части, отдельно от серверной.
  • Проверьте, сколько раз запускается ваш обработчик за одно нажатие.
Где разместить процедуру?

Мини-кейс: проведение по кнопке

Задача: пользователь выделил в списке 30 документов и нажимает «Провести». Плохой вариант: клиент в цикле вызывает серверную процедуру для каждого документа. Итог: 30 обращений, на каждом форма пересобирает контекст, на удалённом канале ожидание растягивается на десятки секунд.

Хороший вариант: клиент берёт массив выделенных ссылок и отправляет его одним вызовом &НаСервереБезКонтекста. Сервер в цикле получает объекты, проводит их и возвращает структуру: сколько проведено и какие документы не прошли. Клиент показывает итог одним сообщением.

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

Ошибки при серверном вызове: как их ловить и показывать

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

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

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

Почему так:

  • 📝 В журнал регистрации уходит полный текст со стеком. По нему администратор найдёт причину.
  • 💬 Пользователю возвращается краткое сообщение, которое можно показать в форме.
  • 🚦 Клиент проверяет поле Успех и решает, что делать дальше.

Какие ошибки встречаются чаще

СимптомПричина
Значение не может быть передано на серверПередан несериализуемый объект (запрос, выборка)
Процедура не найденаНет директивы, нет флага «Вызов сервера» или экспорта
Форма «тормозит» при каждом действииЛишние &НаСервере, вызовы в цикле
На клиенте не видно измененийИзменили локальную копию, а не реквизит формы
Таймаут или обрыв соединенияДолгая операция внутри одного вызова

Не оставляйте пустой блок Исключение. Проглоченная ошибка превращается в «ничего не произошло», и найти причину потом почти невозможно.

Долгие операции

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

Версия платформы и режим совместимости

Поведение вызовов зависит от двух вещей: версии платформы и режима совместимости конфигурации.

Что меняется:

  • ⏳ Синхронные и асинхронные методы. Раньше клиентский код мог «остановиться» на вопросе пользователю, пока тот не ответит. В современных режимах модальные окна запрещены, и такие методы заменены на вызовы с оповещением (ОписаниеОповещения). Код, написанный по старой схеме, в новом режиме перестаёт работать.
  • 🔧 Доступные методы. Часть методов появляется только с определённой версии. Если у конфигурации установлен старый режим совместимости, новые возможности недоступны, даже если сама платформа новая.
  • 🧠 Оптимизации платформы. Новые релизы ускоряют сериализацию и работу с данными формы, но выигрыш получают не все конфигурации.

Что проверить перед повышением режима совместимости:

  1. Поиск модальных вызовов (Вопрос, ОткрытьФормуМодально и подобных) в своих доработках.
  2. Внешние обработки и расширения: они часто содержат устаревшие приёмы.
  3. Тестовая копия базы с прогоном основных сценариев.

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

Проверка серверного кода перед выпуском
Выполнено: 0 / 5

Частые вопросы

Чем серверный вызов отличается от обычного вызова процедуры?
Обычный вызов выполняется внутри одного процесса. Серверный пересекает границу клиент и сервер: данные упаковываются, идут по сети и распаковываются. Поэтому он на порядки дороже.
Нужно ли ставить Знач для всех параметров?
Для входных данных, которые вы не собираетесь менять, да. Так платформа не возвращает их обратно клиенту. Если параметр должен вернуть изменённое значение, Знач не ставят.
Можно ли вызвать клиентскую процедуру из серверной?
Напрямую нет. Сервер возвращает результат, а клиент по нему сам вызывает нужный клиентский код.
Хранит ли сервер переменные между вызовами?
Нет. Локальные переменные теряются после завершения вызова. Постоянное состояние держат в реквизитах формы, параметрах сеанса или в базе.
Как узнать, сколько серверных вызовов делает моя форма?
Включите технологический журнал или замер производительности, выполните действие и посмотрите на число серверных событий за один шаг.