Клиент, сервер и граница между ними: механизм вызовов в 1С 8.3

· 10 мин

Содержание

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

Механизм одинаков для всех конфигураций на управляемых формах: БП 3.0, ЗУП 3.1, УТ 11 и самописных решений. Разница только в том, где работает сервер: внутри процесса клиента (файловая база), на отдельной машине (клиент-серверный вариант) или в облаке за веб-сервером.

Что происходит при вызове сервера в 1С

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

Клиент не умеет работать с базой напрямую. В тонком и веб-клиенте нет объектов Запрос, ДокументОбъект, ТаблицаЗначений. Поэтому любое обращение к данным — это вызов сервера.

Что происходит за один вызов:

  1. 📦 Клиент сериализует параметры (и данные формы, если вызов контекстный).
  2. 🌐 Пакет уходит на сервер: по TCP в клиент-серверном варианте, по HTTP через веб-сервер в облаке.
  3. ⚙️ Рабочий процесс выполняет серверную процедуру.
  4. 📦 Результат и изменённые данные формы сериализуются обратно.
  5. ⏳ Всё это время клиент ждёт: интерфейс не отвечает.

В локальной сети один вызов обычно занимает единицы миллисекунд. Через интернет — десятки и сотни. Двадцать вызовов по 100 мс — это уже две секунды на одно нажатие кнопки.

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

Как устроен серверный вызов на уровне платформы

Где выполняется код, определяют директивы компиляции в модуле формы и свойства общих модулей.

Директивы в модуле формы

ДирективаГде выполняетсяЧто передаётся
&НаКлиентена клиентеничего, вызова нет
&НаСерверена серверепараметры + данные формы туда и обратно
&НаСервереБезКонтекстана серверетолько параметры и возвращаемое значение
&НаКлиентеНаСервереБезКонтекстатам, откуда вызваливызова сервера нет

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

Внеконтекстная процедура формы не видит. Ей доступно только то, что передали параметрами. Зато пакет маленький.

Общие модули с флагом «Вызов сервера»

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

Остальные флаги общего модуля, которые влияют на вызовы:

  • 🔒 Привилегированный — код выполняется без проверки прав. Удобно, но опасно: проверяйте входные параметры.
  • 🔁 Повторное использование возвращаемых значений — На время вызова или На время сеанса. Платформа кеширует результат экспортных функций по набору параметров.
  • 💻 Клиент (управляемое приложение) — модуль доступен на клиенте; вызова сервера при обращении к нему нет.

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

Какие значения проходят через границу

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

Синхронные, асинхронные и фоновые вызовы

Вызов сервера из клиентского кода всегда синхронный: клиент ждёт, пока сервер вернёт ответ. Слово «асинхронный» в 1С относится к другим вещам.

Асинхронный клиентский код

ОписаниеОповещения и ключевые слова Асинх / Ждать (появились в платформе 8.3.18) нужны для диалогов: вопросов, выбора файла, ввода значения. Код не блокирует интерфейс в ожидании ответа пользователя. На скорость серверных вызовов это не влияет: серверная функция внутри асинхронной процедуры выполняется так же синхронно.

Фоновые задания

Если операция идёт дольше 2–3 секунд (пересчёт, загрузка, формирование большого отчёта), её выносят в фоновое задание. Клиент делает короткий вызов «запусти», получает идентификатор задания и периодически спрашивает статус через обработчик ожидания. Интерфейс при этом живой.

В типовых конфигурациях на БСП для этого есть общий модуль ДлительныеОперации и клиентский ДлительныеОперацииКлиент: они берут на себя запуск, ожидание и показ индикатора. В самописных решениях работают напрямую с ФоновыеЗадания.

ВариантКогда подходитЧем платите
Синхронный вызовбыстрые операции до 1–2 секундинтерфейс ждёт
Фоновое заданиедолгие расчёты, загрузки, отчётысложнее код, результат через хранилище
Регламентное заданиеработа по расписанию без пользователяв файловой базе выполняется, только пока запущен клиент

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

Большая часть проблем — не падения, а медленная работа. Но и сообщения об ошибках тоже встречаются.

Диагностика: выберите симптом

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

Оптимизация серверных вызовов: что даёт эффект

Порядок — от самого выгодного к частным приёмам.

  1. Уберите вызовы из циклов. Соберите все данные на клиенте в массив, отправьте одним вызовом, верните Соответствие. Это самый частый источник тормозов.
  2. Объединяйте последовательные вызовы. Если обработчик сначала проверяет данные на сервере, потом считает, потом записывает — сделайте одну серверную процедуру, которая вернёт структуру с результатами.
  3. Выбирайте &НаСервереБезКонтекста, где форма не нужна. Получить курс валюты, проверить ИНН, найти элемент по коду — данные формы для этого не нужны.
  4. Кешируйте справочные значения. Константы, настройки пользователя, признаки функциональных опций редко меняются. Модуль с повторным использованием На время сеанса вызовет сервер один раз за сеанс на каждый набор параметров.
  5. Передавайте на клиент только нужное. Не возвращайте объект целиком, если клиенту нужно два поля.
  6. Делайте больше в ПриСозданииНаСервере. Этот обработчик уже выполняется на сервере при открытии формы. Всё, что форма знает заранее (видимость полей, значения по умолчанию, флаги), рассчитывайте там, а не отдельным вызовом из ПриОткрытии.
  7. Долгое — в фон. Всё, что дольше пары секунд, выносите в фоновое задание.

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

Примеры кода: как писать вызовы правильно

Плохо: вызов сервера в цикле

&НаКлиенте
Процедура ЗаполнитьЦены(Команда)
	Для Каждого Строка Из Объект.Товары Цикл
		Строка.Цена = ЦенаНоменклатуры(Строка.Номенклатура, Объект.ТипЦен);
	КонецЦикла;
КонецПроцедуры

&НаСервереБезКонтекста
Функция ЦенаНоменклатуры(Номенклатура, ТипЦен)
	// один запрос на одну строку
КонецФункции

На 200 строках это 200 вызовов. В облаке с задержкой 80 мс — около 16 секунд ожидания.

Хорошо: один вызов на всю табличную часть

&НаКлиенте
Процедура ЗаполнитьЦены(Команда)
	ЗаполнитьЦеныНаСервере();
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьЦеныНаСервере()

	СписокНоменклатуры = Новый Массив;
	Для Каждого Строка Из Объект.Товары Цикл
		СписокНоменклатуры.Добавить(Строка.Номенклатура);
	КонецЦикла;

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

	Цены = Новый Соответствие;
	Выборка = Запрос.Выполнить().Выбрать();
	Пока Выборка.Следующий() Цикл
		Цены.Вставить(Выборка.Номенклатура, Выборка.Цена);
	КонецЦикла;

	Для Каждого Строка Из Объект.Товары Цикл
		Цена = Цены.Получить(Строка.Номенклатура);
		Строка.Цена = ?(Цена = Неопределено, 0, Цена);
	КонецЦикла;

КонецПроцедуры

Один вызов и один запрос. Имена регистра и измерений возьмите из своей конфигурации: в примере структура регистра цен с измерениями «Тип цен» и «Номенклатура».

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

Хорошо: внеконтекстная проверка

&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
	Реквизиты = РеквизитыКонтрагента(Объект.Контрагент);
	Объект.Договор = Реквизиты.ОсновнойДоговор;
	Элементы.ГруппаНерезидент.Видимость = Реквизиты.Нерезидент;
КонецПроцедуры

&НаСервереБезКонтекста
Функция РеквизитыКонтрагента(Контрагент)
	Результат = Новый Структура("ОсновнойДоговор, Нерезидент");
	// заполнение полей запросом по справочнику контрагентов
	Возврат Результат;
КонецФункции

Одна функция возвращает всё, что нужно обработчику, структурой. Данные формы по сети не идут.

Фоновое задание без БСП

&НаСервере
Функция ЗапуститьРасчетНаСервере()
	Параметры = Новый Массив;
	Параметры.Добавить(Объект.Ссылка);
	Задание = ФоновыеЗадания.Выполнить("РасчетыСервер.ВыполнитьРасчет", Параметры, , "Расчёт по документу");
	Возврат Задание.УникальныйИдентификатор;
КонецФункции

&НаСервереБезКонтекста
Функция ЗаданиеЗавершено(Идентификатор)
	Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
	Возврат Задание = Неопределено
		Или Задание.Состояние <> СостояниеФоновогоЗадания.Активно;
КонецФункции

На клиенте сохраните идентификатор в реквизит формы и подключите ПодключитьОбработчикОжидания с интервалом 1–2 секунды. Обработчик вызывает ЗаданиеЗавершено, а после завершения отключается и забирает результат. Процедура ВыполнитьРасчет должна быть экспортной и лежать в серверном общем модуле.

Каждая проверка статуса — тоже вызов сервера. Не ставьте интервал меньше секунды: при десятке пользователей это заметная нагрузка.

Отладка и мониторинг серверных вызовов

Сначала посчитайте вызовы, потом оптимизируйте. Интуиция здесь часто обманывает.

Счётчик вызовов в клиенте

Запустите клиент с параметром командной строки /DisplayPerformance. В строке состояния появится число вызовов сервера и объём переданных данных за последнее действие. Тот же режим включается в Конфигураторе в параметрах запуска 1С:Предприятия: найдите флажок показа количества вызовов сервера.

Ориентир для обычной формы: нажатие кнопки или изменение поля — один вызов, открытие формы — один-два. Если видите десять и больше на одно действие, есть что править.

Замер производительности

В Конфигураторе запустите отладку и включите замер производительности (Отладка → Замер производительности). Результат покажет, какие строки кода выполнялись дольше всего и сколько раз. Серверные строки в замере видны, если отладка сервера разрешена.

Технологический журнал

На рабочем сервере технологический журнал настраивают файлом logcfg.xml. Для вызовов полезны события:

  • 📥 CALL — входящий вызов на сервере: длительность, объём, контекст;
  • 📤 SCALL — исходящий вызов;
  • 🗄️ DBMSSQL / DBPOSTGRS — запросы к СУБД внутри вызова.

Фильтруйте по длительности (Duration), иначе журнал быстро займёт диск. Держите его включённым только на время диагностики.

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

Вызов сервера в облаке 1С:Fresh и при разработке в EDT

1С:Fresh и другие облака

В сервисе клиент общается с сервером через веб-сервер по HTTP. Задержка между пользователем и сервером складывается из интернета и публикации. Поэтому в облаке разница между хорошим и плохим кодом видна сильнее всего:

СценарийЛокальная сетьОблако, задержка ~80 мс
1 вызов на действиенезаметно~0,1 с
10 вызовов на действие~0,05 с~0,8 с
200 вызовов в цикле~1 с~16 с

Цифры ориентировочные: реальное время зависит от канала и объёма данных. Логика простая: в облаке время ответа почти целиком определяется числом вызовов, а не мощностью сервера.

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

1С:EDT

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

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

Чем вызов сервера отличается от обращения к базе данных?
Вызов сервера — переход кода с клиента на сервер. Обращение к базе — запрос, который сервер делает к СУБД. За один вызов сервер может выполнить десятки запросов, и это нормально.
Считается ли открытие формы вызовом сервера?
Да. При открытии управляемой формы платформа создаёт её на сервере, выполняет ПриСозданииНаСервере и передаёт форму на клиент. Поэтому подготовку данных удобно делать именно там.
Можно ли вызвать процедуру `&НаСервере` из `&НаСервереБезКонтекста`?
Нет. Внеконтекстная процедура не имеет доступа к форме, поэтому контекстные процедуры формы ей недоступны. Общие серверные модули — доступны.
Почему в файловой базе всё быстро, а на сервере медленно?
В файловом варианте сервер работает в процессе клиента, сетевой передачи нет. После переноса на сервер каждый вызов начинает стоить реального времени, и вызовы в циклах становятся заметны.
Нужен ли флаг «Вызов сервера» для серверных процедур, которые вызываются только с сервера?
Нет. Флаг нужен только тем модулям, к которым обращается клиентский код. Чем меньше модулей с этим флагом, тем меньше точек входа с клиента и тем проще контролировать безопасность.