Сервер хочет что-то сделать на клиенте: как это устроено в 1С

· 9 мин

Содержание

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

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

Как 1С делит код на клиентский и серверный

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

ДирективаГде выполняетсяЧто доступно
&НаКлиентеВ клиентском приложенииЭлементы формы, диалоги, окна, Сообщить
&НаСервереНа сервере, с контекстом формыРеквизиты формы, объекты базы, запросы
&НаСервереБезКонтекстаНа сервере, без контекста формыТолько переданные параметры и база
&НаКлиентеНаСервереБезКонтекстаИ там и тамТолько то, что есть в обоих контекстах

Правило вызовов простое:

  • 🟢 клиент вызывает серверную процедуру;
  • 🟢 сервер вызывает серверные процедуры и общие модули;
  • 🔴 сервер не вызывает клиентские процедуры, у него нет к ним доступа.

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

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

Пять рабочих схем вместо прямого вызова

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

Способ 1. Вернуть результат и вызвать клиентскую процедуру самому

Это самый частый и самый надёжный вариант. Клиентская процедура вызывает сервер, получает данные и сразу после этого выполняет нужное действие на клиенте.

&НаКлиенте
Процедура РассчитатьСумму(Команда)
	Результат = РассчитатьСуммуНаСервере(Объект.Количество, Объект.Цена);
	ПоказатьПредупреждение(, "Итого: " + Формат(Результат, "ЧДЦ=2; ЧГ=0"));
КонецПроцедуры

&НаСервереБезКонтекста
Функция РассчитатьСуммуНаСервере(Количество, Цена)
	Возврат Количество * Цена;
КонецФункции

Серверная функция ничего не знает о клиенте. Она возвращает значение, а решение «что показать» принимает клиентская процедура. Если результатов несколько, возвращайте структуру:

&НаСервере
Функция ВыполнитьПроверку()
	Результат = Новый Структура;
	Результат.Вставить("Успешно", Ложь);
	Результат.Вставить("Сообщение", "Не заполнен контрагент");
	Возврат Результат;
КонецФункции

На клиенте разбираете поля структуры и вызываете любую клиентскую процедуру в зависимости от флага Успешно.

Подходит, когда сервер отработал быстро (до нескольких секунд) и клиент ждёт ответа.

Способ 2. Передать на сторону клиента «что делать» через данные

Если после серверной операции нужно выбрать одно из нескольких действий на клиенте, не пытайтесь «вызвать» их с сервера. Верните код действия и выполните выбор на клиенте.

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

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

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

Способ 3. Оповещение формы после серверной работы

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

&НаКлиенте
Процедура ЗаписатьИОповестить(Команда)
	ЗаписатьНаСервере();
	Оповестить("ИзмененКонтрагент", Объект.Ссылка, ЭтотОбъект);
КонецПроцедуры

В форме, которая должна отреагировать, пишут обработчик:

&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
	Если ИмяСобытия = "ИзмененКонтрагент" Тогда
		Элементы.Список.Обновить();
	КонецЕсли;
КонецПроцедуры

Оповещение работает только внутри одного клиентского сеанса. Форма другого пользователя его не получит.

Второй встроенный механизм — описание оповещения. Он нужен для асинхронных диалогов и закрытия форм: вы создаёте Новый ОписаниеОповещения("ИмяПроцедуры", ЭтотОбъект, ДополнительныеПараметры) и передаёте в метод, который вызовет вашу процедуру после завершения (ПоказатьВопрос, ОткрытьФорму с оповещением о закрытии). Для своей логики вызвать его вручную можно через ВыполнитьОбработкуОповещения(Оповещение, Результат).

Способ 4. Долгая операция: фоновое задание и опрос

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

В конфигурациях с библиотекой стандартных подсистем (БСП) для этого есть общий модуль ДлительныеОперации. Схема такая:

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

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

&НаКлиенте
Процедура ПослеВыгрузки(Результат, ДополнительныеПараметры) Экспорт
	Если Результат = Неопределено Тогда
		Возврат; // задание отменено
	КонецЕсли;
	Если Результат.Статус = "Выполнено" Тогда
		ПоказатьПредупреждение(, "Выгрузка завершена");
	Иначе
		ПоказатьПредупреждение(, "Ошибка: " + Результат.КраткоеПредставлениеОшибки);
	КонецЕсли;
КонецПроцедуры

Здесь «вызов клиентской процедуры с сервера» — это ПослеВыгрузки. Её запускает не сервер, а клиентский код БСП, когда фоновое задание закончилось.

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

Без БСП тот же приём собирают вручную: запускают фоновое задание, на клиенте подключают ПодключитьОбработчикОжидания("ПроверитьФон", 3), а в обработчике проверяют состояние и при готовности вызывают ОтключитьОбработчикОжидания("ПроверитьФон").

Способ 5. Серверное событие для другого пользователя

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

  • 🔄 Опрос. Клиент раз в несколько секунд обращается к серверу через обработчик ожидания и забирает накопленные события (например, из регистра сведений или очереди в данных). Простой и предсказуемый вариант. Интервал меньше 3-5 секунд для интерфейса избыточен: нагрузка растёт, а выгоды нет.
  • 💬 Система взаимодействия. Подсистема обсуждений и уведомлений платформы. Подходит для сообщений людям, но не заменяет вызов процедуры в форме.

Сам по себе обработчик ожидания — клиентский механизм. Он вызывает ваш клиентский код по таймеру, а данные берёт с сервера. Так и достигается эффект «сервер сказал клиенту».

Ещё один частый случай — серверный обработчик события объекта, например ПриЗаписи в модуле документа. Он выполняется на сервере и в модуль формы не заглядывает. Если после записи нужно что-то показать, пользуйтесь обработчиком формы ПослеЗаписи или ПослеЗаписиНаСервере: он клиентский, и в нём вы уже знаете результат записи. Для проверок заполнения серверный модуль объекта вызывает Отказ = Истина и Сообщить(...), а форма сама покажет сообщения, не требуя вызова клиентской процедуры.

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

Что можно передавать между клиентом и сервером

Параметры серверных процедур и возвращаемые значения передаются копированием. Сервер получает независимую копию, а не ссылку на объект клиента. Поэтому изменения параметра внутри серверной процедуры клиент увидит только если вы вернули значение.

Передаются без проблем:

  • ✅ примитивные типы: число, строка, дата, булево;
  • ✅ ссылки на объекты базы;
  • ✅ Структура, Массив, Соответствие с допустимыми значениями внутри;
  • ✅ ТаблицаЗначений, ХранилищеЗначения, ДвоичныеДанные.

Не передаются объекты, которые существуют только в одном из контекстов: элементы формы, ОписаниеОповещения, COM-объекты, открытые файловые потоки. Попытка передать такое значение приводит к ошибке при вызове.

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

&НаСервере
Процедура ОбработатьСписок(Знач СписокСсылок)
	// клиент получит назад только изменения реквизитов формы, список обратно не возвращается
КонецПроцедуры

Возвращаемые значения: что и как отдавать

Несколько приёмов, которые упрощают жизнь:

  1. Для одного значения используйте функцию с Возврат.
  2. Для нескольких — возвращайте Структуру с понятными именами ключей, а не массив с «магическими» индексами.
  3. Для больших таблиц не гоняйте данные туда-обратно. Положите результат во временное хранилище (ПоместитьВоВременноеХранилище) и верните клиенту адрес, а на второй стороне получите данные через ПолучитьИзВременногоХранилища.
  4. Ошибки не прячьте: серверный код вызывает ВызватьИсключение, клиентский ловит Попытка ... Исключение и показывает КраткоеПредставлениеОшибки(ИнформацияОбОшибке()).
&НаКлиенте
Процедура ВыполнитьДействие(Команда)
	Попытка
		ВыполнитьДействиеНаСервере();
	Исключение
		ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
	КонецПопытки;
КонецПроцедуры

Типичные ошибки и что с ними делать

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

Практические рекомендации

  • 📏 Делайте меньше серверных вызовов, но не дробите их мелко. Каждый вызов — сетевой обмен, и на медленном канале десять мелких вызовов заметно хуже одного.
  • 🧱 Выносите бизнес-логику в серверные общие модули, а в форме оставляйте только оркестрацию: собрать параметры, вызвать, показать результат.
  • 🚫 Не используйте &НаСервере там, где контекст формы не нужен: &НаСервереБезКонтекста быстрее, потому что не передаёт на сервер реквизиты формы.
  • ⏱ Для всего, что дольше нескольких секунд, сразу закладывайте фоновое выполнение. Переделывать потом сложнее.
  • 🧪 Проверяйте код в режиме тонкого клиента, а не только в толстом. Различия контекстов лучше всего видны именно там.
Выбор схемы взаимодействия
Выполнено: 0 / 5

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

Можно ли вызвать клиентскую процедуру из серверной напрямую?
Нет. Серверный код не имеет доступа к клиентским процедурам. Верните результат на клиент и вызовите процедуру там.
Как показать сообщение пользователю из серверной процедуры?
Используйте Сообщить или Новый СообщениеПользователю с вызовом Сообщить(). Сообщение появится у клиента, который вызвал сервер.
Чем `&НаСервереБезКонтекста` отличается от `&НаСервере`?
Первая не получает данные формы и работает быстрее. Реквизиты формы ей нужно передавать параметрами.
Как узнать, что фоновое задание закончилось?
Через оповещение при ожидании завершения в БСП либо через обработчик ожидания, который периодически проверяет состояние задания.
Что делать, если нужно уведомить пользователя в другом сеансе?
Записать событие в данные на сервере, а клиент другого пользователя будет читать его по таймеру через обработчик ожидания.