При разработке интерактивных интерфейсов в системе 1С:Предприятие часто возникает задача оперативного оповещения пользователя о событиях, происходящих на сервере. Это может быть готовность тяжелого отчета, появление нового заказа в базе данных, изменение статуса задачи или новое сообщение в чате. Классический подход с использованием метода ПодключитьОбработчикОжидания имеет существенный недостаток: при каждом обращении клиента к серверу через контекстные или бесконтекстные вызовы курсор мыши пользователя на доли секунды превращается в «колёсико» (песочные часы). Если опрос происходит часто (например, раз в секунду), интерфейс начинает раздражающе моргать, усложняя комфортную работу.
В этой статье мы подробно разберем, почему возникает этот эффект, и проанализируем пять эффективных способов реализации фонового обмена данными между сервером и клиентом 1С, которые позволяют полностью избавиться от мерцания курсора и блокировки интерфейса.
При вызове серверной процедуры из кода управляемой формы (даже с директивой &НаСервереБезКонтекста) платформа 1С временно переводит поток клиентского приложения в режим ожидания ответа. Платформа сигнализирует операционной системе о выполнении блокирующей сетевой операции. Операционная система Windows или Linux реагирует на это изменением стандартного указателя мыши на системный курсор занятости. Даже если серверная процедура абсолютно пустая и выполняется за 1-2 миллисекунды, визуальный стек ОС успевает отрисовать анимацию загрузки. Результатом становится постоянное микро-моргание экрана.
Для решения этой проблемы нам необходимо исключить прямые синхронные вызовы сервера 1С из контекста выполнения клиентской формы. Рассмотрим, как это сделать программными методами.
---
Это одно из самых простых и изящных решений, не требующее изменения версии платформы или установки стороннего программного обеспечения. Суть метода заключается в том, что вместо прямого обращения к серверным процедурам конфигурации клиент делает обычный HTTP-запрос к опубликованному HTTP-сервису собственной базы 1С.
Платформа выполняет запросы через объект HTTPСоединение на низком сетевом уровне, не переводя поток интерфейса формы в состояние блокирующего серверного вызова. Благодаря этому операционная система не меняет курсор мыши, и интерфейс для пользователя остается абсолютно статичным и комфортным.
Рассмотрим по шагам, как реализовать этот механизм:
Шаг 1. Создание и публикация HTTP-сервиса
В дереве метаданных конфигурации добавим новый объект HTTP-сервисы с именем ОбменКлиентСервер. Укажем корневой URL как exchange. Добавим шаблон URL с именем ПолучитьДанные и шаблоном /get_data/{ИмяПользователя}. Для этого шаблона определим метод GET и укажем имя обработчика в модуле HTTP-сервиса.
В модуле HTTP-сервиса напишем код, который будет проверять наличие подготовленных данных для конкретного пользователя во временном хранилище или специальном регистре сведений:
Функция ПолучитьДанныеGET(Запрос)
ИмяПользователя = Запрос.ПараметрыURL.Получить("ИмяПользователя");
// Получаем данные, подготовленные для этого клиента на сервере
ДанныеДляОтправки = ПроверитьНовыеСобытияНаСервере(ИмяПользователя);
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ДанныеДляОтправки);
СтрокаJSON = ЗаписьJSON.Закрыть();
Ответ.УстановитьТелоИзСтроки(СтрокаJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Возврат Ответ;
КонецФункции
После написания кода необходимо опубликовать HTTP-сервис на веб-сервере (IIS или Apache) через стандартный диалог публикации в Конфигураторе.
Шаг 2. Настройка опроса на стороне клиента
В модуле управляемой формы при открытии подключаем стандартный обработчик ожидания с небольшим интервалом (например, 1 или 2 секунды):
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ПодключитьОбработчикОжидания("ПроверитьОбновленияЧерезHTTP", 2, Ложь);
КонецПроцедуры
В процедуре обработчика ожидания мы создаем асинхронное подключение к нашему опубликованному веб-серверу и отправляем GET-запрос:
&НаКлиенте
Процедура ПроверитьОбновленияЧерезHTTP()
// Параметры подключения к нашему веб-серверу
СерверАдрес = "localhost";
Порт = 80;
БазаИмя = "Base1C";
HTTPСоединение = Новый HTTPСоединение(СерверАдрес, Порт, "ПользовательРобот", "ПарольРобота");
HTTPЗапрос = Новый HTTPЗапрос("/" + БазаИмя + "/hs/exchange/get_data/" + ИмяПользователяПриложения);
Попытка
HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос);
Если HTTPОтвет.КодСостояния = 200 Тогда
СтрокаJSON = HTTPОтвет.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
ОбработатьПолученныеДанные(СтрокаJSON);
КонецЕсли;
Исключение
// Подавляем сетевые ошибки, чтобы не прерывать сеанс пользователя
ЗаписьЖурналаРегистрации("Ошибка фонового опроса HTTP", УровеньЖурналаРегистрации.Предупреждение,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
Благодаря такому подходу опрос выполняется незаметно для операционной системы. Курсор мыши не меняет своего представления, и пользователь может комфортно вводить данные, пока система раз в секунду обменивается информацией с сервером.
---
Если мы хотим полностью разгрузить основной поток 1С от выполнения периодических запросов, мы можем переложить эту задачу на встроенный в платформу браузерный движок. Поле HTML-документа работает в собственном изолированном процессе операционной системы, что дает нам колоссальные преимущества.
Мы можем разместить на форме скрытое или маленькое поле HTML-документа, внутри которого будет крутиться легковесный JavaScript-сценарий. Этот сценарий может взаимодействовать с внешним веб-сервером по протоколу WebSockets или технологии SSE (Server-Sent Events).
Разберем детально схему реализации этого метода:
Строка и видом отображения Поле HTML-документа.Посмотрим на пример генерации HTML-содержимого для инициализации сокета:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Формируем HTML с JS-скриптом подключения к WebSocket-серверу
ТекстHTML =
"<html>
|<head>
|<script>
| var socket;
| function initConnect() {
| socket = new WebSocket('ws://localhost:8080/events');
| socket.onmessage = function(event) {
| // Передаем данные обратно в платформу 1С
| window.location.href = 'v8event:' + encodeURIComponent(event.data);
| };
| socket.onclose = function() {
| setTimeout(initConnect, 5000); // Автореконнект при обрыве связи
| };
| }
| window.onload = initConnect;
|</script>
|</head>
|<body style='background-color:transparent;margin:0;padding:0;'></body>
|</html>";
СодержимоеHTMLПоля = ТекстHTML;
КонецПроцедуры
Для приема сообщений из JavaScript на стороне 1С мы используем стандартное событие поля HTML-документа ПриНажатии (в старых версиях платформы через парсинг изменения навигации) или событие УправлениеКартой/ПриНажатии. В современных версиях платформы (начиная с 8.3.14) доступна более простая схема взаимодействия через объект window.v8exception или явный вызов процедур 1С из кода JavaScript:
&НаКлиенте
Процедура СодержимоеHTMLПоляПриНажатии(Элемент, ДанныеСобытия, СтандартнаяОбработка)
СсылкаПерехода = ДанныеСобытия.Document.location.href;
Если Найти(СсылкаПерехода, "v8event:") > 0 Тогда
СтандартнаяОбработка = Ложь;
ДанныеСтрокой = Сред(СсылкаПерехода, Найти(СсылкаПерехода, "v8event:") + 8);
ДекодированныеДанные = ДекодироватьСтроку(ДанныеСтрокой, СпособКодированияСтроки.КодировкаURL);
// Обрабатываем событие, пришедшее из сокета в реальном времени!
ВыполнитьДействиеПоСобытию(ДекодированныеДанные);
КонецЕсли;
КонецПроцедуры
Этот подход гарантирует 100% отсутствие блокировок и мерцания интерфейса, так как сетевое соединение удерживается силами изолированного браузерного движка.
---
Начиная с версии платформы 8.3.11, разработчики 1С представили штатный механизм асинхронного двустороннего взаимодействия — Систему взаимодействия. Это полноценный сервер сообщений, глубоко интегрированный в платформу.
Многие разработчики ошибочно полагают, что для использования Системы взаимодействия обязательно разворачивать локальный сервер (1С:Сервер взаимодействия), требующий отдельного лицензирования и администрирования СУБД PostgreSQL/Elasticsearch. На самом деле для большинства прикладных задач можно бесплатно использовать облачный сервис 1С:Диалоги, предоставляемый самой фирмой «1С».
При использовании Системы взаимодействия доставка сообщений с сервера на конкретный клиент происходит мгновенно по постоянному TCP-соединению, которое платформа удерживает в фоновом режиме. Никакого мерцания курсора при этом не происходит.
Проанализируем пример отправки сообщения с сервера конкретному пользователю:
&НаСервере
Процедура ОтправитьУведомлениеПользователю(АдресатИмя, ТекстУведомления)
// Получаем пользователя Системы взаимодействия по его имени в ИБ
ПользовательСВ = СистемыВзаимодействия.ПолучитьПользователя(Новый УникальныйИдентификатор(АдресатИмя));
Если ПользовательСВ <> Неопределено Тогда
// Находим или создаем непубличное обсуждение между сервером (системой) и пользователем
КлючОбсуждения = "Уведомления_" + АдресатИмя;
Обсуждение = СистемыВзаимодействия.СоздатьОбсуждение();
Обсуждение.Ключ = КлючОбсуждения;
Обсуждение.Участники.Добавить(ПользовательСВ);
Обсуждение.Записать();
// Отправляем сообщение в обсуждение
НовоеСообщение = СистемыВзаимодействия.СоздатьСообщение(Обсуждение.Идентификатор);
НовоеСообщение.Текст = ТекстУведомления;
НовоеСообщение.Записать();
КонецЕсли;
КонецПроцедуры
Для получения этих сообщений на клиенте используется обработчик интеграции или подписка на события сообщений в модуле управляемого приложения:
&НаКлиенте
Процедура ПриНачалеРаботыСистемы()
ПодключитьОбработчикНовыхСообщений();
КонецПроцедуры
&НаКлиенте
Процедура ПодключитьОбработчикНовыхСообщений()
// Регистрируем обработчик для входящих сообщений Системы взаимодействия
СистемаВзаимодействия.ПодключитьОбработчикНовыхСообщений(Новый ОписаниеОповещения("ОбработатьНовоеСообщение", ЭтотОбъект));
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьНовоеСообщение(Сообщение, ДополнительныеПараметры) Экспорт
// Отображаем уведомление пользователю без перезагрузки формы
ПоказатьОповещениеПользователя("Новое событие на сервере!",, Сообщение.Текст);
КонецПроцедуры
Использование встроенной Системы взаимодействия — наиболее перспективный и поддерживаемый вендором способ организации асинхронного обмена данными.
---
Если ваша конфигурация работает в режиме совместимости с ранними версиями платформы (например, 8.3.6, как у автора темы на форуме), встроенная Система взаимодействия вам недоступна. В этом случае идеальным технологическим решением будет использование технологии NativeAPI и создание внешней компоненты на C++ или C#.
Суть метода заключается в делегировании сетевой работы операционной системе за рамками процесса 1С:
ExternalEvent.ОбработкаВнешнегоСобытия модуля управляемого приложения или формы.Посмотрим на пример инициализации внешней компоненты в модуле 1С:
&НаКлиенте
Процедура ПриОткрытииФормы()
// Подключаем внешнюю компоненту, написанную на C# или C++
Попытка
УстановитьВнешнююКомпоненту("ОбщиеМакеты.КомпонентаАсинхронногоОбмена");
ПодключитьВнешнююКомпоненту("ОбщиеМакеты.КомпонентаАсинхронногоОбмена", "AsyncSocket", ТипВнешнейКомпоненты.Native);
Компонента = Новый("AddIn.AsyncSocket.SocketClient");
// Передаем параметры подключения и запускаем прослушивание порта в фоне
Компонента.ОткрытьСоединение("192.168.1.100", 9000, ИмяПользователяПриложения);
Исключение
ЗаписьЖурналаРегистрации("Внешняя компонента", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
После этого на форме достаточно определить обработчик внешнего события:
&НаКлиенте
Процедура ОбработкаВнешнегоСобытия(Источник, Событие, Данные)
Если Источник = "AsyncSocket" И Событие = "NewMessage" Тогда
// Данные пришли с сервера мгновенно, без опроса! Курсор не моргал!
ОбработатьПолученныеДанные(Данные);
КонецЕсли;
КонецПроцедуры
Это самый производительный способ для старых платформ, полностью снимающий нагрузку с сетевой подсистемы 1С на клиенте.
---
Если у вас есть возможность обновить версию платформы 1С до актуального релиза, то проблема решается штатными методами на современном технологическом стеке:
Асинхронные HTTP-запросы (с платформы 8.3.21)
В платформу были добавлены неблокирующие методы для работы с HTTP. Теперь при вызове методов ВызватьHTTPМетодАсинх() или ПолучитьАсинх() с использованием конструкции Ждать, платформа не блокирует интерфейсный поток операционной системы. Фоновый опрос выполняется полностью незаметно для пользователя.
Штатный WebSocket-клиент (с платформы 8.3.27)
В платформу добавлен долгожданный объект WebSocketКлиент. С его помощью 1С может держать постоянное двустороннее соединение с любым внешним сервером сокетов (например, RabbitMQ, сервером телефонии или NodeJS-сервером) напрямую из кода 1С без использования полей HTML-документа или сторонних DLL. Доставка событий происходит мгновенно, не нагружает сеть циклическими опросами и гарантирует идеальное поведение курсора мыши.
---
Для того чтобы вам было проще определиться с выбором архитектурного решения под ваш проект, проанализируем сравнительную таблицу преимуществ и недостатков каждого подхода:
Выбирайте наиболее подходящий способ в зависимости от требований вашего проекта и ограничений используемой версии платформы 1С:Предприятие. Успешной разработки!