В процессе разработки прикладных решений на платформе 1С:Предприятие в управляемом приложении программисты часто сталкиваются с фундаментальным ограничением клиент-серверной архитектуры: сервер не может по своей инициативе вызвать клиентские процедуры. Классическая схема работы подразумевает, что любой сеанс связи всегда инициируется клиентом.
Однако на практике крайне необходим механизм обратного вызова (коллбэка). Например, когда сервер выполняет тяжелый расчет, формирует сложный отчет в фоновом режиме или ожидает изменения внешних данных, нам нужно мгновенно оповестить об этом конкретного пользователя, не блокируя его интерфейс. В этой статье мы подробно разберем эволюцию подходов к решению этой задачи в 1С — от классических «опросов» до современных асинхронных push-уведомлений.
Давайте рассмотрим самый старый и проверенный временем способ, который работает на абсолютно любых версиях платформы 1С v8.3. Суть метода заключается в имитации обратного вызова: мы запускаем длительный процесс на сервере в виде фонового задания, а на клиенте организуем периодический опрос состояния этого задания.
Разберем по шагам, как это реализовать программно:
ПодключитьОбработчикОжидания.Посмотрим на пример реализации кода на клиенте и сервере:
// КЛИЕНТСКАЯ ЧАСТЬ ФОРМЫ
&НаКлиенте
Перем ИдентификаторЗадания;
&НаКлиенте
Процедура ЗапуститьРасчетНаСервере(Команда)
// Запускаем длительный процесс на сервере
РезультатЗапуска = ЗапуститьФоновоеЗаданиеНаСервере();
ИдентификаторЗадания = РезультатЗапуска.ИдентификаторЗадания;
// Подключаем опрос сервера каждые 2 секунды
ПодключитьОбработчикОжидания("ПроверитьСтатусВыполнения", 2, Ложь);
Состояние("Выполняется расчет, пожалуйста, подождите...");
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьСтатусВыполнения()
// Запрашиваем состояние фонового задания на сервере
ЗаданиеВыполнено = ПроверитьСтатусЗаданияНаСервере(ИдентификаторЗадания);
Если ЗаданиеВыполнено Тогда
// Отключаем цикличный опрос, чтобы не нагружать систему
ОтключитьОбработчикОжидания("ПроверитьСтатусВыполнения");
// Оповещаем пользователя о завершении процесса
ПоказатьОповещениеПользователя("Успешно!", , "Расчет на сервере полностью завершен!");
ОбновитьДанныеНаФорме();
КонецЕсли;
КонецПроцедуры
На сервере мы запускаем фоновое задание стандартным образом через менеджер фоновых заданий:
// СЕРВЕРНАЯ ЧАСТЬ
&НаСервереБезКонтекста
Функция ЗапуститьФоновоеЗаданиеНаСервере()
ПараметрыЗапуска = Новый Массив;
// Передаем параметры для расчета, если необходимо
Задание = ФоновыеЗадания.Выполнить("МойМодульВычислений.ВыполнитьДлительныйРасчет", ПараметрыЗапуска);
Результат = Новый Структура;
Результат.Вставить("ИдентификаторЗадания", Задание.УникальныйИдентификатор);
Возврат Результат;
КонецФункции
&НаСервереБезКонтекста
Функция ПроверитьСтатусЗаданияНаСервере(Знач УИДЗадания)
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(УИДЗадания);
Если Задание <> Неопределено Тогда
Возврат (Задание.Состояние = СостояниеФоновогоЗадания.Выполнено);
КонецЕсли;
Возврат Ложь;
КонецФункции
Недостатки подхода: Этот метод создает постоянную паразитную нагрузку на сервер и сеть (так называемый polling). Каждые несколько секунд клиент шлет запросы на сервер. Кроме того, реакция на событие происходит с задержкой, равной интервалу опроса.
С выходом платформы 8.3.11 разработчики получили долгожданную альтернативу — Систему взаимодействия. Этот механизм работает по протоколу WebSockets, что позволяет серверу мгновенно отправлять push-сообщения на клиентское приложение.
Проанализируем ситуацию: как использовать этот инструмент для сервисных целей? Мы можем создать скрытое служебное обсуждение, на которое клиент подписывается при старте сеанса. Когда на сервере происходит важное событие, сервер отправляет сообщение в это обсуждение, а клиент мгновенно его обрабатывает.
Рассмотрим подробнее программную реализацию этой схемы:
// КЛИЕНТ: ПОДПИСКА НА СОБЫТИЯ СЕРВЕРА
&НаКлиенте
Процедура ПриНачалеРаботыСистемы()
// Подключаем обработчик сообщений из нашей служебной группы
ИдентификаторОбсуждения = ПолучитьСлужебноеОбсуждениеНаСервере();
Если ЗначениеЗаполнено(ИдентификаторОбсуждения) Тогда
СистемаВзаимодействия.ПодключитьОбработчикНовыхСообщений(
ИдентификаторОбсуждения,
Новый ОписаниеОповещения("ОбработатьСобытиеСервера", ЭтотОбъект)
);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьСобытиеСервера(Сообщение, ДополнительныеПараметры) Экспорт
// Анализируем текст или свойства сообщения
Если Сообщение.Текст = "ВыполнитьОбновлениеИнтерфейса" Тогда
// Код обновления формы или оповещения пользователя
ПоказатьОповещениеПользователя("Внимание!", , "Сервер обновил критически важные данные.");
КонецЕсли;
КонецПроцедуры
На сервере отправка сообщения выглядит чрезвычайно просто:
// СЕРВЕР: ИНИЦИАЦИЯ ВЫЗОВА КЛИЕНТА
&НаСервере
Процедура ОтправитьСобытиеНаКлиент(ИдентификаторОбсуждения)
НовоеСообщение = СистемаВзаимодействия.СоздатьСообщение(ИдентификаторОбсуждения);
НовоеСообщение.Текст = "ВыполнитьОбновлениеИнтерфейса";
НовоеСообщение.Записать();
КонецПроцедуры
Критика подхода: Несмотря на то что этот механизм полностью убирает задержки и экономит ресурсы процессора, он требует развертывания отдельного тяжелого компонента — «1С:Сервера взаимодействия» (с базой данных PostgreSQL и поисковым кластером Elasticsearch) либо интеграции с внешним облачным сервисом «1С:Диалог». Для простых локальных баз или закрытых контуров безопасности это решение часто избыточно и сложно в администрировании.
В начале 2024 года фирма «1С» представила по-настоящему революционный механизм, который разработчики ждали долгие годы. В платформе 8.3.26 появился встроенный объект МенеджерУведомленийКлиента. Он позволяет отправлять легкие push-сообщения напрямую из кода сервера в конкретные активные сеансы пользователей без использования Системы взаимодействия.
Давайте разберем по шагам работу с этим великолепным инструментом:
Шаг 1. Настройка приема на клиенте. При открытии формы или при запуске программы мы регистрируем обработчик для определенного ключа (темы) уведомлений.
&НаКлиенте
Процедура ПриОткрытии(Отказ)
// Регистрируем обработчик для ключа "УведомленияРасчетаПродаж"
МенеджерУведомленийКлиента.ПодключитьОбработчик(
"УведомленияРасчетаПродаж",
Новый ОписаниеОповещения("ПринятьУведомлениеОтСервера", ЭтотОбъект)
);
КонецПроцедуры
&НаКлиенте
Процедура ПринятьУведомлениеОтСервера(ДанныеУведомления, ДополнительныеПараметры) Экспорт
// Извлекаем переданные сервером параметры
ТекстДляВывода = ДанныеУведомления.Параметры.Получить("ТекстСообщения");
// Выводим информацию пользователю на экран
ПоказатьОповещениеПользователя("Оповещение системы", , ТекстДляВывода);
КонецПроцедуры
Шаг 2. Отправка уведомления с сервера. На сервере мы можем отправить уведомление конкретному сеансу пользователя (или списку сеансов). Идентификатор сеанса можно получить через стандартные механизмы платформы.
&НаСервереБезКонтекста
Процедура ВыполнитьОтправкуПушаНаКлиент(ИдентификаторСеансаПолучателя)
// Подготавливаем данные для передачи на клиент
ДанныеУведомления = Новый Соответствие;
ДанныеУведомления.Вставить("ТекстСообщения", "Ваш отчет успешно сформирован и доступен в разделе 'Мои документы'!");
// Создаем объект уведомления
НовоеУведомление = Новый УведомлениеКлиента(
"УведомленияРасчетаПродаж", // Ключ, на который подписан клиент
ДанныеУведомления
);
// Формируем список получателей (идентификаторы сеансов)
СписокПолучателей = Новый Массив;
СписокПолучателей.Добавить(ИдентификаторСеансаПолучателя);
// Отправляем уведомление силами самого кластера 1С
МенеджерУведомленийКлиента.Отправить(СписокПолучателей, НовоеУведомление);
КонецПроцедуры
Для того чтобы сделать правильный выбор архитектурного решения в вашем проекте, проанализируем сильные и слабые стороны всех трех подходов:
Таким образом, если ваша компания использует современные версии платформы 1С:Предприятие, настоятельно рекомендуется полностью отказаться от использования периодических обработчиков ожидания для отслеживания фоновых процессов и перейти на нативный объект МенеджерУведомленийКлиента. Это позволит значительно снизить нагрузку на рабочие процессы кластера серверов 1С и повысить отзывчивость интерфейса для конечных пользователей.