Сервер не звонит клиенту: как доставить сообщение из серверного кода 1С 8.3
Содержание
Сервер 1С не умеет сам обратиться к клиенту. Сообщение попадает на клиент только в ответ на вызов, который начал клиент: либо как результат серверной функции, либо как накопленные сообщения СообщениеПользователю, которые платформа выводит после возврата управления. Всё остальное — оповещения, всплывающие окна, обновление форм — делается уже на клиенте по данным, которые вернул сервер.
Если серверная работа идёт долго или вообще без участия пользователя (фоновое или регламентное задание), клиент сам опрашивает сервер через ПодключитьОбработчикОжидания. Для сообщений другим пользователям есть система взаимодействия. Ниже — каждый способ с кодом для управляемых форм платформы 8.3.
Как устроен обмен между клиентом и сервером в 1С 8.3
Код модуля формы делится директивами компиляции: &НаКлиенте, &НаСервере, &НаСервереБезКонтекста, &НаКлиентеНаСервереБезКонтекста. Каждый вызов серверной процедуры из клиентской — это один запрос к серверу. Клиент ждёт ответа, сервер выполняет код и возвращает управление.
Главное ограничение: из серверного кода нельзя вызвать клиентский метод. Попытка написать в &НаСервере что-то вроде ПоказатьОповещениеПользователя() или Оповестить() закончится ошибкой: эти методы недоступны на сервере.
Что сервер может отдать клиенту за один вызов:
- 📦 Возвращаемое значение функции — число, строку, структуру, массив (сериализуемые значения).
- 📝 Изменённые реквизиты формы — при вызове
&НаСервереконтекст формы уходит на сервер и возвращается обратно. - 💬 Сообщения пользователю — накопленные
СообщениеПользователюиСообщить(). - ⚠️ Исключение — если сервер вызвал
ВызватьИсключение, оно «пролетает» на клиент. - 🗄️ Адрес во временном хранилище — для больших данных: файлов, таблиц.
Каждый серверный вызов стоит времени, особенно в веб-клиенте и через интернет. Собирайте всё, что нужно клиенту, в одну структуру и возвращайте одним вызовом, а не дёргайте сервер в цикле.
Какой способ выбрать
Сообщения через СообщениеПользователю: самый короткий путь с сервера
Объект СообщениеПользователю создаётся на сервере, а показывается на клиенте. Платформа складывает сообщения в очередь и выводит их, когда серверный вызов завершился.
&НаСервере
Процедура ПроверитьДокументНаСервере()
Если Не ЗначениеЗаполнено(Объект.Контрагент) Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Не заполнен контрагент";
Сообщение.Поле = "Объект.Контрагент";
Сообщение.Сообщить();
КонецЕсли;
КонецПроцедуры
Свойство Поле подсвечивает реквизит формы: пользователь щёлкает по сообщению и попадает в нужное поле. Если сообщение формируется в модуле объекта (например, в ОбработкаПроверкиЗаполнения), вместо Поле = "Объект..." указывают имя реквизита объекта и вызывают Сообщение.УстановитьДанные(ЭтотОбъект).
В типовых конфигурациях на Библиотеке стандартных подсистем есть обёртка, которую стоит использовать вместо ручного создания объекта:
ОбщегоНазначения.СообщитьПользователю("Не заполнен контрагент", , "Контрагент", "Объект");
Её клиентский двойник — ОбщегоНазначенияКлиент.СообщитьПользователю.
Метод Сообщить() по-прежнему работает, но к полю не привязывается и не позволяет задать данные. В новом коде используйте СообщениеПользователю.
Чего этот способ не умеет:
- ❌ показать сообщение до окончания серверного вызова — пока сервер считает, клиент «молчит»;
- ❌ доставить текст другому пользователю или в другой сеанс;
- ❌ вывести всплывающее окно в углу экрана.
Возврат значения и всплывающее уведомление ПоказатьОповещениеПользователя
Если нужно уведомление в углу экрана, серверная функция возвращает данные, а показывает их клиент. Метод ПоказатьОповещениеПользователя доступен только на клиенте.
&НаКлиенте
Процедура Пересчитать(Команда)
Результат = ПересчитатьНаСервере();
Если Результат.Успешно Тогда
ПоказатьОповещениеПользователя(
"Пересчёт выполнен",
,
СтрШаблон("Обработано строк: %1", Формат(Результат.Количество, "ЧГ=0")),
БиблиотекаКартинок.Информация);
Иначе
ПоказатьПредупреждение(, Результат.ТекстОшибки);
КонецЕсли;
КонецПроцедуры
&НаСервере
Функция ПересчитатьНаСервере()
Результат = Новый Структура("Успешно, Количество, ТекстОшибки", Ложь, 0, "");
Для Каждого Строка Из Объект.Товары Цикл
Строка.Сумма = Строка.Количество * Строка.Цена;
Результат.Количество = Результат.Количество + 1;
КонецЦикла;
Результат.Успешно = Истина;
Возврат Результат;
КонецФункции
Параметры ПоказатьОповещениеПользователя по порядку: текст, навигационная ссылка (по щелчку откроется объект), пояснение, картинка. Ссылку на объект получают на сервере функцией ПолучитьНавигационнуюСсылку(Ссылка) и возвращают в той же структуре.
💡 Возвращайте структуру, а не голую строку. Тогда к результату легко добавить флаг успеха, количество, ссылку — без переделки вызова.
Оповестить и обработчик ОбработкаОповещения в форме
Оповестить рассылает событие всем открытым формам текущего клиентского приложения. Форма ловит его в обработчике ОбработкаОповещения. Так обновляют список, когда в другой форме записали элемент, или перечитывают остатки после проведения документа.
Метод клиентский, поэтому схема всегда одна: сервер возвращает данные → клиент вызывает Оповестить.
// Форма, которая отправляет событие
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
Оповестить("ИзмененыЦеныНоменклатуры", Объект.Ссылка, ЭтотОбъект);
КонецПроцедуры
// Форма, которая принимает событие
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если ИмяСобытия = "ИзмененыЦеныНоменклатуры" Тогда
ОбновитьЦеныНаСервере(Параметр);
КонецЕсли;
КонецПроцедуры
Обработчик ОбработкаОповещения добавляют через палитру свойств формы, на закладке событий. В нём вызываются все события, поэтому начинайте с проверки ИмяСобытия — иначе форма будет делать лишнюю работу на каждое оповещение.
Если форма типовая, не правьте её модуль напрямую. Обработчик добавляют в расширении конфигурации: в расширенной форме назначают своё действие на событие ОбработкаОповещения с вызовом «Перед» или «После» типового.
Оповестить не выходит за пределы одного клиентского приложения. Пользователь на соседнем компьютере это событие не получит.
Для объектов есть вариант короче: ОповеститьОбИзменении(Ссылка). Динамические списки, где отображается этот объект, перечитаются сами, без своего обработчика.
Оператор Выполнить: почему не стоит передавать код с сервера
Встречается приём: сервер возвращает строку с кодом на встроенном языке, а клиент исполняет её через Выполнить(). Формально это «управление клиентом с сервера». На практике — источник проблем.
| Риск | Что происходит |
|---|---|
| 🔓 Безопасность | Если в строку попадает текст из данных (наименование, комментарий), его можно превратить в исполняемый код |
| 🐞 Отладка | Ошибки в строке не видны при синтаксическом контроле, всплывают только при выполнении |
| 🧩 Сопровождение | Код размазан по строковым литералам, поиск по модулям его не находит |
| 🌐 Совместимость | Набор доступных методов зависит от вида клиента; строка, рабочая в тонком клиенте, может упасть в веб-клиенте или мобильном |
Безопасная замена — вернуть с сервера не код, а имя действия и параметры, и выбрать ветку на клиенте:
&НаКлиенте
Процедура ВыполнитьКомандуСервера(Ответ)
Если Ответ.Действие = "ОткрытьФорму" Тогда
ОткрытьФорму(Ответ.ИмяФормы, Ответ.Параметры);
ИначеЕсли Ответ.Действие = "Оповестить" Тогда
Оповестить(Ответ.ИмяСобытия, Ответ.Параметр);
ИначеЕсли Ответ.Действие = "Уведомление" Тогда
ПоказатьОповещениеПользователя(Ответ.Текст);
КонецЕсли;
КонецПроцедуры
Если всё же нужен вызов «по имени», для асинхронных цепочек есть ОписаниеОповещения и ВыполнитьОбработкуОповещения(Описание, Результат): обработчик задаётся явно, процедурой в модуле, и проверяется компилятором.
Если Выполнить неизбежен (например, в обработке-конструкторе формул), включайте безопасный режим: УстановитьБезопасныйРежим(Истина) перед выполнением чужого кода на сервере.
Сообщения из фонового задания и долгих операций
Пока серверный вызов не закончился, клиент ничего не показывает. Для операций на минуты работу выносят в фоновое задание, а клиент раз в несколько секунд спрашивает, как дела.
Запуск задания
&НаСервере
Функция ЗапуститьЗагрузкуНаСервере()
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
Параметры = Новый Массив;
Параметры.Добавить(АдресРезультата);
Задание = ФоновыеЗадания.Выполнить("ЗагрузкаДанных.Выполнить", Параметры, , "Загрузка прайса");
ИдентификаторЗадания = Задание.УникальныйИдентификатор;
Возврат АдресРезультата;
КонецФункции
Метод ЗагрузкаДанных.Выполнить — экспортная процедура общего серверного модуля. Внутри неё обычный СообщениеПользователю.Сообщить() не теряется: сообщения копятся в фоновом задании.
Опрос с клиента
&НаКлиенте
Процедура Загрузить(Команда)
АдресРезультата = ЗапуститьЗагрузкуНаСервере();
ПодключитьОбработчикОжидания("ПроверитьЗадание", 2, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьЗадание()
Состояние = СостояниеЗаданияНаСервере(ИдентификаторЗадания);
Если Состояние.Завершено Тогда
ПоказатьОповещениеПользователя("Загрузка завершена");
ИначеЕсли Состояние.ТекстОшибки <> "" Тогда
ПоказатьПредупреждение(, Состояние.ТекстОшибки);
Иначе
ПодключитьОбработчикОжидания("ПроверитьЗадание", 2, Истина);
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция СостояниеЗаданияНаСервере(Идентификатор)
Результат = Новый Структура("Завершено, ТекстОшибки", Ложь, "");
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
Для Каждого Сообщение Из Задание.ПолучитьСообщенияПользователю(Истина) Цикл
Сообщение.Сообщить();
КонецЦикла;
Если Задание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
Результат.Завершено = Истина;
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.ЗавершеноАварийно Тогда
Результат.ТекстОшибки = КраткоеПредставлениеОшибки(Задание.ИнформацияОбОшибке);
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Отменено Тогда
Результат.ТекстОшибки = "Задание отменено";
КонецЕсли;
Возврат Результат;
КонецФункции
Реквизит формы ИдентификаторЗадания имеет тип УникальныйИдентификатор. Параметр Истина в ПолучитьСообщенияПользователю удаляет полученные сообщения из задания, чтобы при следующем опросе они не пришли повторно. Повторно вызванный на сервере Сообщение.Сообщить() передаёт их в окно сообщений клиента.
💡 Интервал опроса 1–2 секунды на старте, затем его можно увеличивать. Опрос каждые 0,1 секунды от десятков пользователей заметно грузит сервер.
В конфигурациях на БСП всю эту обвязку делает модуль ДлительныеОперации: запуск, опрос с нарастающим интервалом, прогресс, сообщения. Своё колесо имеет смысл писать только в самостоятельной конфигурации.
Как сообщить другому пользователю или из регламентного задания
У регламентного задания нет клиента вовсе, у другого пользователя — свой сеанс. Здесь два рабочих варианта.
1. Очередь в базе + опрос. Сервер пишет запись в регистр сведений
(пользователь, текст, дата, флаг «прочитано»). У пользователя в модуле управляемого приложения или в начальной форме висит ПодключитьОбработчикОжидания с интервалом 30–60 секунд: он забирает непрочитанное одним запросом и показывает через ПоказатьОповещениеПользователя. Просто, предсказуемо, работает на любой платформе 8.3.
2. Система взаимодействия. Механизм обсуждений, встроенный в платформу: сообщения доходят до пользователей почти сразу и видны в панели обсуждений. Нужна регистрация информационной базы на сервере взаимодействия; проверить её можно методом СистемаВзаимодействия.ИнформационнаяБазаЗарегистрирована(). Состав методов зависит от версии платформы — сверяйтесь с синтакс-помощником по объекту СистемаВзаимодействия.
| Способ | Задержка | Кому доставляет | Что нужно |
|---|---|---|---|
| Возврат значения | сразу после вызова | инициатору | ничего |
СообщениеПользователю | после вызова | инициатору | ничего |
Оповестить | мгновенно | формам этого же клиента | данные с сервера |
| Фоновое задание + опрос | интервал опроса | инициатору | реквизит формы, обработчик ожидания |
| Регистр + опрос | 30–60 секунд | любому пользователю | регистр сведений, обработчик ожидания |
| Система взаимодействия | секунды | любому пользователю | регистрация базы на сервере взаимодействия |
Ошибки на сервере: как они доходят до клиента и как их перехватить
Исключение на сервере прерывает вызов и передаётся клиенту. Если его не поймать, пользователь увидит окно ошибки с текстом и, возможно, стеком вызовов. Для ожидаемых ситуаций это плохой интерфейс.
Три правила, которые закрывают большую часть случаев:
- Ожидаемые ошибки — через результат. Не заполнено поле, нет остатка, файл не того формата — возвращайте
Успешно = Ложьи текст, а не вызывайте исключение. - Неожиданные — ловите на сервере и пишите в журнал. Пользователю — короткий текст, администратору — подробности.
ВызватьИсключение— когда операцию нужно прервать целиком, в том числе с откатом транзакции. Текст исключения пользователь увидит дословно, пишите его по-человечески.
&НаСервере
Функция ВыгрузитьНаСервере()
Результат = Новый Структура("Успешно, ТекстОшибки", Ложь, "");
Попытка
ВыполнитьВыгрузку();
Результат.Успешно = Истина;
Исключение
Информация = ИнформацияОбОшибке();
ЗаписьЖурналаРегистрации("Выгрузка данных",
УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(Информация));
Результат.ТекстОшибки = КраткоеПредставлениеОшибки(Информация);
КонецПопытки;
Возврат Результат;
КонецФункции
Типичные ловушки при передаче сообщений:
- 🔁 Сообщение не показалось. Код выполнялся в фоновом задании, а сообщения из задания никто не забрал через
ПолучитьСообщенияПользователю. - 🕳️ Синтаксический контроль ругается, что процедура не определена, хотя
ПоказатьОповещениеПользователяилиОповеститьнаписаны верно, — метод вызван в серверной процедуре. - 📭 Форма не обновилась после
Оповестить. Имя события в отправителе и вОбработкаОповещенияполучателя написано по-разному: сравнение строк точное. - 🐢 Форма «зависает» на долгой операции. Работа идёт прямо в серверном вызове; переносите её в фоновое задание.
- 📦 Ошибка передачи значения на клиент. Сервер вернул несериализуемое значение (
ТаблицаЗначений, объект запроса,ДокументОбъект). Переложите данные в массив структур или поместите во временное хранилище.