Серверный вызов в управляемой форме 1С: что происходит между кликом и ответом
Содержание
Серверный вызов в 1С — это обращение клиентского кода к процедуре или функции, которая выполняется на сервере 1С:Предприятия. Клиент отправляет параметры по сети, сервер выполняет код и возвращает результат. Каждый такой вызов стоит времени: сеть, сериализация данных, создание контекста. Поэтому от числа и «веса» вызовов зависит, насколько отзывчивой будет форма.
Клиентский код живёт в тонком или веб-клиенте, серверный — в процессе рабочего сервера (rphost). Между ними всегда есть граница, и пересекать её дёшево не получится. Дальше разберём, как устроена эта граница, чем различаются директивы, что реально передаётся и как сократить число обращений.
Что физически происходит при вызове
Когда пользователь нажимает кнопку, срабатывает клиентский обработчик. Если внутри есть вызов процедуры с директивой &НаСервере, платформа делает несколько шагов:
- Собирает параметры вызова и, для
&НаСервере, данные формы. - Сериализует их и отправляет на сервер по сети.
- Сервер поднимает контекст формы (или готовит вызов без контекста).
- Выполняется ваш серверный код.
- Результат и изменённые данные формы упаковываются и едут обратно.
- Клиент применяет изменения и перерисовывает форму.
Клиент всё это время ждёт. Пока серверный код не вернул управление, форма «замирает», хотя сервер может работать быстро.
Из чего складывается время одного вызова:
- 🌐 Сеть. В локальной сети это единицы миллисекунд. По удалённому каналу или через веб-публикацию — десятки и сотни.
- 📦 Сериализация. Чем больше данных формы и параметров, тем дольше упаковка и распаковка.
- ⚙️ Подготовка контекста. Для вызова с контекстом формы сервер восстанавливает состояние формы.
- 🧮 Сам код. Запросы, циклы, проведение документов.
Реальные цифры зависят от канала и нагрузки. Ориентир: на удалённом подключении один «пустой» серверный вызов заметно медленнее, чем в локальной сети, а если их сотня в цикле, пользователь увидит паузу в несколько секунд.
Важная деталь: между вызовами сервер не хранит локальные переменные вашего кода. Вы вернулись на клиент, значит всё, что не передали и не сохранили в реквизитах формы или параметрах сеанса, потеряно.
Состояние между вызовами
Раз сервер ничего не помнит, у данных есть три «дома»:
- 🗂️ Реквизиты формы. Живут, пока открыта форма, и ездят с
&НаСервере. - 🔑 Параметры сеанса. Хранятся на сервере на время сеанса пользователя. Подходят для текущего подразделения или настроек, но не для крупных наборов данных.
- 🗄️ База данных. Единственное надёжное место для всего, что должно пережить закрытие формы.
Хранить большие массивы в реквизитах формы невыгодно: они поедут по сети при каждом вызове с контекстом.
Директивы компиляции: где что выполняется
Директива перед процедурой определяет, где она компилируется и откуда её можно вызывать. В управляемой форме их четыре:
| Директива | Где выполняется | Есть ли контекст формы | Когда применять |
|---|---|---|---|
&НаКлиенте | Клиент | Да | Работа с интерфейсом, обработчики событий |
&НаСервере | Сервер | Да, форма передаётся целиком | Когда нужно читать и менять реквизиты формы |
&НаСервереБезКонтекста | Сервер | Нет | Запросы и расчёты, которым форма не нужна |
&НаКлиентеНаСервереБезКонтекста | Обе стороны | Нет | Небольшие универсальные функции без обращений к форме |
Самое дорогое — &НаСервере. Платформа пересылает состояние формы в обе стороны, даже если вы обратились к одному реквизиту. На тяжёлой форме с большими табличными частями это ощутимо.
&НаСервереБезКонтекста значительно легче: передаются только параметры, которые вы явно указали. Если процедуре не нужны реквизиты формы, ставьте эту директиву по умолчанию.
Области видимости
- Клиентская процедура формы может вызвать серверную процедуру той же формы.
- Серверная процедура формы не может вызвать клиентскую: сервер не знает о клиенте.
- Общий модуль доступен с клиента только если у него установлен флаг «Вызов сервера», а сам он серверный.
- Клиентские общие модули нельзя вызвать с сервера.
Если нужен вызов из клиентского кода в общий модуль, сделайте серверный общий модуль с флагами «Сервер» и «Вызов сервера». Не включайте «Клиент» вместе с «Вызов сервера» для одного и того же кода без нужды: так легко получить путаницу в том, где что исполняется.
Параметры и результат: что реально летит по сети
Внутри одной стороны параметры можно менять «по месту», но при пересечении границы работает другое правило. Данные копируются, и изменённые значения возвращаются обратно клиенту, если параметр не помечен словом Знач.
Это значит:
- 🔁 Параметр без
Значпри серверном вызове едет на сервер и возвращается обратно в клиентскую переменную. Лишний трафик, если вы его только читаете. - ➡️ Параметр со
Значедет только туда. Для входных данных это оптимально. - ⬅️ Результат функции возвращается всегда. Нужно вернуть несколько значений, используйте структуру или параметры без
Знач.
Что можно передавать: примитивы, ссылки, структуры, массивы, соответствия, таблицы значений, списки значений и другие типы, которые платформа умеет сериализовать.
Что нельзя: объекты, привязанные к серверной сессии, например Запрос, РезультатЗапроса, выборки. Такие значения не пересекают границу. Платформа выдаст ошибку о невозможности передачи значения.
Не передавайте на клиент больше, чем нужно для отображения. Таблица на 50 тысяч строк, возвращённая «на всякий случай», превращается в секунды ожидания и расход памяти клиента.
Пример: считаем остатки на сервере без контекста формы и возвращаем только итог.
&НаКлиенте
Процедура ПоказатьОстаток(Команда)
Остаток = ПолучитьОстатокНаСервере(Товар, Склад);
Сообщить("Остаток: " + Остаток);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьОстатокНаСервере(Знач Товар, Знач Склад)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(СУММА(Остатки.КоличествоОстаток), 0) КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Товар = &Товар И Склад = &Склад) КАК Остатки";
Запрос.УстановитьПараметр("Товар", Товар);
Запрос.УстановитьПараметр("Склад", Склад);
Выборка = Запрос.Выполнить().Выбрать();
Возврат ?(Выборка.Следующий(), Выборка.Остаток, 0);
КонецФункции
Имена регистра в вашей конфигурации будут другими. Суть в том, что наружу выходит одно число, а не набор данных.
Как сократить число вызовов и их вес
Главное правило: один пользовательский шаг, один серверный вызов. Если на кнопку приходится три обращения к серверу подряд, их нужно склеить в одно.
Типичные антипаттерны
- 🔄 Серверный вызов внутри цикла на клиенте. Цикл по 200 строкам даёт 200 обращений.
- 📞 Несколько подряд вызовов, где результат первого нужен только для второго. Объедините на сервере.
- 📋 Вызов на каждое изменение поля, хотя можно собрать все изменения и отправить пачкой.
- 🧾 Передача всей формы через
&НаСервере, когда достаточно двух параметров.
Как переписать цикл
Было: клиент идёт по строкам и для каждой спрашивает сервер о цене. Стало: клиент собирает ссылки на номенклатуру в массив, один раз отправляет на сервер, получает соответствие «номенклатура → цена» и расставляет цены локально.
На сервере в таком случае делается один запрос с условием В (&МассивНоменклатуры), а не сотня мелких.
Кеширование
Если данные почти не меняются (настройки, справочные признаки), загрузите их один раз и храните в реквизите формы или в переменной модуля формы. Для общих модулей есть повторное использование возвращаемых значений: серверные функции с таким режимом кешируют результат на время вызова или сеанса. Подходит для неизменяемых справочных данных, но не для остатков и цен.
Как понять, что вызовов слишком много:
- В технологическом журнале серверные вызовы фиксируются отдельными событиями. Анализируйте их частоту в пределах одного действия.
- В «Замере производительности» смотрите время клиентской части, отдельно от серверной.
- Проверьте, сколько раз запускается ваш обработчик за одно нажатие.
&НаКлиенте. Серверного кода здесь нет.&НаСервереБезКонтекста с параметрами Знач.&НаСервере. Эта директива передаёт форму целиком, поэтому не вызывайте её лишний раз.&НаКлиентеНаСервереБезКонтекста или общий модуль с нужными флагами.Мини-кейс: проведение по кнопке
Задача: пользователь выделил в списке 30 документов и нажимает «Провести». Плохой вариант: клиент в цикле вызывает серверную процедуру для каждого документа. Итог: 30 обращений, на каждом форма пересобирает контекст, на удалённом канале ожидание растягивается на десятки секунд.
Хороший вариант: клиент берёт массив выделенных ссылок и отправляет его одним вызовом &НаСервереБезКонтекста. Сервер в цикле получает объекты, проводит их и возвращает структуру: сколько проведено и какие документы не прошли. Клиент показывает итог одним сообщением.
Выигрыш даёт не быстрее работающий код, а отсутствие 29 лишних пересечений границы. Если проведение занимает долго, этот же сервер-вызов выносят в фоновое задание.
Ошибки при серверном вызове: как их ловить и показывать
Исключение на сервере доходит до клиента. Если вы его не перехватили, пользователь увидит стандартное окно с текстом ошибки. Чаще всего это нежелательно: текст техничный, а контекст потерян.
Перехватывайте ошибку там, где можете осмысленно ответить:
&НаСервереБезКонтекста
Функция ВыполнитьОперацию(Знач Параметр)
Попытка
// Рабочий код
Возврат Новый Структура("Успех, Текст", Истина, "");
Исключение
ЗаписьЖурналаРегистрации("Операция.Ошибка",
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат Новый Структура("Успех, Текст", Ложь,
КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецФункции
Почему так:
- 📝 В журнал регистрации уходит полный текст со стеком. По нему администратор найдёт причину.
- 💬 Пользователю возвращается краткое сообщение, которое можно показать в форме.
- 🚦 Клиент проверяет поле
Успехи решает, что делать дальше.
Какие ошибки встречаются чаще
| Симптом | Причина |
|---|---|
| Значение не может быть передано на сервер | Передан несериализуемый объект (запрос, выборка) |
| Процедура не найдена | Нет директивы, нет флага «Вызов сервера» или экспорта |
| Форма «тормозит» при каждом действии | Лишние &НаСервере, вызовы в цикле |
| На клиенте не видно изменений | Изменили локальную копию, а не реквизит формы |
| Таймаут или обрыв соединения | Долгая операция внутри одного вызова |
Не оставляйте пустой блок Исключение. Проглоченная ошибка превращается в «ничего не произошло», и найти причину потом почти невозможно.
Долгие операции
Если серверный код считает минутами, держать клиента в вызове нельзя. Сеанс заблокирован, веб-соединение может оборваться по таймауту. Выносите такую работу в фоновое задание и опрашивайте состояние с клиента. В типовых конфигурациях для этого есть подсистема длительных операций, воспользуйтесь ею, а не пишите свой механизм.
Версия платформы и режим совместимости
Поведение вызовов зависит от двух вещей: версии платформы и режима совместимости конфигурации.
Что меняется:
- ⏳ Синхронные и асинхронные методы. Раньше клиентский код мог «остановиться» на вопросе пользователю, пока тот не ответит. В современных режимах модальные окна запрещены, и такие методы заменены на вызовы с оповещением (
ОписаниеОповещения). Код, написанный по старой схеме, в новом режиме перестаёт работать. - 🔧 Доступные методы. Часть методов появляется только с определённой версии. Если у конфигурации установлен старый режим совместимости, новые возможности недоступны, даже если сама платформа новая.
- 🧠 Оптимизации платформы. Новые релизы ускоряют сериализацию и работу с данными формы, но выигрыш получают не все конфигурации.
Что проверить перед повышением режима совместимости:
- Поиск модальных вызовов (
Вопрос,ОткрытьФормуМодальнои подобных) в своих доработках. - Внешние обработки и расширения: они часто содержат устаревшие приёмы.
- Тестовая копия базы с прогоном основных сценариев.
Точные номера версий платформы и ограничения конкретных режимов смотрите в документации к вашей версии. Сверяйтесь с релизом, под которым работаете, а не с памятью.
Частые вопросы
Чем серверный вызов отличается от обычного вызова процедуры?
Нужно ли ставить Знач для всех параметров?
Знач не ставят.