Как реализовать бесконечный цикл в фоне для получения данных с вебсокета в 1С без блокировки интерфейса?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с внешними компонентами или интерактивными данными, например, получаемыми через WebSocket, мы часто сталкиваемся с проблемой блокировки пользовательского интерфейса. Когда код выполнения занимает значительное время или должен постоянно "слушать" внешние события, это приводит к тому, что форма 1С перестает отвечать на действия пользователя. В этой статье мы подробно рассмотрим, как организовать фоновое выполнение такого кода, чтобы интерфейс оставался отзывчивым, и данные могли интерактивно отображаться на форме.

Изначальная проблема, с которой столкнулся наш коллега, заключалась в использовании цикла вида Пока Клиент.Принять(0, Данные) Цикл ... КонецЦикла; непосредственно при открытии формы. Такой подход, хоть и функционален для получения данных, полностью блокирует основной поток выполнения и делает интерфейс непригодным для работы. Цель же — постоянно получать данные с вебсокета и немедленно отображать их на форме.

Давайте проанализируем различные подходы и выберем наиболее подходящий для нашей задачи, учитывая возможности платформы 1С:Предприятие.

1. Использование Обработчика ожидания (ПодключитьОбработчикОжидания)

Один из самых простых и эффективных способов реализовать периодическую проверку событий на клиентской стороне без блокировки интерфейса – это механизм обработчика ожидания. Он позволяет выполнять определенную процедуру с заданным интервалом, когда система 1С находится в состоянии "покоя", то есть не занята другими интенсивными операциями.

Как работает ПодключитьОбработчикОжидания?

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

  1. Подключение обработчика: Мы вызываем метод ПодключитьОбработчикОжидания(<ИмяПроцедуры>, <Интервал>, <Однократно>).
  2. Процедура-обработчик: Создаем процедуру без параметров, которая будет содержать нашу логику по работе с вебсокетом. В нашем случае, это будет вызов метода Клиент.Принять().
  3. Отключение обработчика: Важно не забывать отключать обработчик ожидания, когда он больше не нужен (например, при закрытии формы), чтобы избежать утечек памяти или нежелательного выполнения кода. Для этого используется метод ОтключитьОбработчикОжидания(<ИмяПроцедуры>).

Преимущества для нашего кейса:

Пример реализации:

Давайте посмотрим, как это будет выглядеть в коде модуля управляемой формы.


&НаКлиенте
Перем мКлиентВебсокета; // Предполагается, что это объект внешней компоненты

// Процедура инициализации и подключения обработчика
Процедура ПриОткрытии()
    // Инициализация объекта Клиент. Предположим, что он создается где-то здесь.
    // Например: мКлиентВебсокета = Новый ВнешняяКомпонентаВебсокет.Клиент();
    // мКлиентВебсокета.Подключиться("ws://localhost:8080"); // Пример подключения

    // Подключаем обработчик ожидания, который будет регулярно проверять вебсокет
    // Интервал 0.1 секунды (100 миллисекунд) достаточно мал для интерактивности
    // и достаточно велик, чтобы не перегружать процессор постоянными вызовами.
    ПодключитьОбработчикОжидания("ПроверитьВебсокет", 0.1, Ложь);
    Сообщить("Обработчик ожидания для вебсокета подключен.");
КонецПроцедуры

// Процедура, выполняемая обработчиком ожидания
&НаКлиенте
Процедура ПроверитьВебсокет()
    Перем Данные;
    // В цикле принимаем все доступные данные, чтобы не ждать следующего вызова обработчика
    // Если Клиент.Принять() возвращает Истина, значит есть новые данные
    Пока мКлиентВебсокета.Принять(0, Данные) Цикл
        // Здесь мы обрабатываем полученные данные
        // и обновляем форму.
        Сообщить("Получены данные: " + Данные);
        // Пример обновления элемента формы (например, ПолеHTMLДокумента или ПолеТекстовогоДокумента)
        // Элементы.МоеПолеДляСообщений.Текст = Элементы.МоеПолеДляСообщений.Текст + Данные + Символы.ПС;
        // Или добавление строки в таблицу на форме
        // НоваяСтрока = Элементы.ТаблицаДанных.Добавить();
        // НоваяСтрока.Сообщение = Данные;
        
        // Важно: избегайте ресурсоемких операций внутри этого цикла,
        // чтобы не вызвать блокировку, которую мы пытаемся избежать.
    КонецЦикла;
КонецПроцедуры

// Процедура отключения обработчика при закрытии формы
Процедура ПриЗакрытии()
    // Отключаем обработчик ожидания, чтобы он не продолжал работать после закрытия формы
    ОтключитьОбработчикОжидания("ПроверитьВебсокет");
    Сообщить("Обработчик ожидания для вебсокета отключен.");
    
    // Также здесь можно корректно закрыть соединение вебсокета
    // мКлиентВебсокета.Закрыть();
КонецПроцедуры

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

Важно: этот подход имитирует "бесконечный цикл" путем частых проверок. Сам метод Клиент.Принять(0, Данные) с таймаутом 0 (или другим маленьким значением) позволяет проверить наличие данных без длительного ожидания, что идеально подходит для такого периодического вызова.

2. Длительные операции (Фоновые задания) с оповещением

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

Как работает механизм длительных операций с оповещением:

  1. Запуск на сервере: Мы инициируем длительную операцию на сервере (например, через ВыполнитьОбработкуВФоне() или НачатьДлительнуюОперацию()).
  2. Ожидание результата на клиенте: На клиентской стороне мы не ждем завершения операции напрямую, а подписываемся на событие ее завершения с помощью ДлительныеОперацииКлиент.ОжидатьЗавершение(), указывая процедуру-обработчик оповещения.
  3. Возврат данных: Фоновое задание выполняет свою работу. Для возврата данных на клиент оно может использовать временное хранилище или параметры оповещения.
  4. Обработка на клиенте: Когда фоновое задание завершается, срабатывает процедура-обработчик оповещения на клиенте. В ней мы получаем результаты и обновляем форму.

Особенности для нашего кейса (вебсокет):

Мы не будем приводить полный пример кода для длительных операций здесь, так как для интерактивного клиентского вебсокета ПодключитьОбработчикОжидания более нагляден, а полноценная реализация серверного вебсокет-прокси с длительными операциями – это достаточно объемная задача.

3. Асинхронные методы (Асинх, Ждать, Обещание) (начиная с версии 8.3.18)

С версии 8.3.18 платформа 1С:Предприятие ввела синтаксис для асинхронных операций, схожий с async/await из JavaScript. Этот подход позволяет писать асинхронный код более последовательно и читабельно, избегая "пирамиды" из вложенных обработчиков.

Как это работает:

Преимущества для нашего кейса:

Важно: Применимость этого подхода напрямую зависит от того, поддерживает ли ваша внешняя компонента для вебсокета асинхронные методы (например, ПринятьАсинх()) или является ли сама операция Клиент.Принять() достаточно быстрой, чтобы не задерживать выполнение, если она не вернет данные сразу. Если внешняя компонента предоставляет асинхронные интерфейсы, этот способ будет наиболее современным и удобным для организации работы с вебсокетом.

Если ваша внешняя компонента не имеет асинхронных методов, то использование Асинх/Ждать с блокирующими вызовами не даст полного асинхронного эффекта и может все равно привести к задержкам. Однако, мы можем использовать его в сочетании с ПодключитьОбработчикОжидания, чтобы асинхронно вызывать более крупные блоки логики, если компонента поддерживает что-то вроде НачатьПриемСообщения и ЗавершитьПриемСообщения.

4. Нативная поддержка WebSocket (начиная с версии 8.3.27)

Наиболее современным, эффективным и рекомендованным подходом для работы с WebSocket в 1С:Предприятии, если ваша версия платформы это позволяет, является использование нативной поддержки протокола, которая появилась начиная с версии 8.3.27.

Как это работает:

Платформа 1С:Предприятие предоставляет новые встроенные объекты языка для работы с WebSocket, а также новый тип объектов метаданных "WebSocket-клиент". Это позволяет нам создавать и управлять WebSocket-соединениями непосредственно средствами 1С, без использования внешних компонент.

Преимущества для нашего кейса:

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

Анализ исходного подхода и критическое замечание

Комментарий из форума "вебСокет не так работает - куеты ты приобрел" указывает на глубокое непонимание или некорректное использование протокола WebSocket в исходном подходе. Протокол WebSocket по своей природе является асинхронным и событийно-ориентированным. Это означает, что клиент не должен постоянно опрашивать сервер на предмет новых данных (как это происходит с блокирующим циклом Пока Клиент.Принять(0, Данные) Цикл). Вместо этого, сервер отправляет данные клиенту, когда они появляются, и клиент должен быть готов их принять через специально разработанные для этого обработчики событий.

Использование блокирующего цикла Пока Клиент.Принять(0, Данные) Цикл, даже с минимальным таймаутом, превращает асинхронный протокол в полусинхронный "опрос", который нагружает систему и приводит к блокировке интерфейса. В идеале внешняя компонента для WebSocket должна была бы предоставлять методы с обработчиками событий (callback-функциями), которые срабатывают при получении нового сообщения. Если такая функциональность отсутствует, то использование ПодключитьОбработчикОжидания с проверкой Клиент.Принять() является компромиссным решением, имитирующим событийную модель, но по сути остающимся polling-ом.

Выводы и рекомендации

Мы видим, что проблема блокировки интерфейса при работе с вебсокетом в 1С решаема несколькими способами. Выбор конкретного подхода зависит от версии вашей платформы и функциональности используемой внешней компоненты:

  1. ПодключитьОбработчикОжидания (для версии 8.3.17 и ниже, или если внешняя компонента не имеет асинхронных методов): Это наиболее доступное и относительно простое решение для клиентской стороны, позволяющее периодически проверять наличие данных и обновлять форму без полной блокировки интерфейса. Мы рекомендуем этот способ как быстрый и эффективный старт.
  2. Длительные операции с оповещением (для серверного WebSocket-прокси или сложных фоновых задач): Этот механизм подойдет, если взаимодействие с вебсокетом должно происходить на сервере 1С, а клиентская форма лишь получает результаты. Однако для постоянного, интерактивного клиентского прослушивания вебсокета его применение менее удобно, чем обработчик ожидания.
  3. Асинхронные методы (Асинх, Ждать) (начиная с 8.3.18, если внешняя компонента поддерживает асинхронные вызовы): Если ваша внешняя компонента для вебсокета предоставляет асинхронные методы, этот подход будет наиболее современным и удобным для написания читаемого неблокирующего кода.
  4. Нативная поддержка WebSocket (начиная с 8.3.27): Это наиболее рекомендуемое и эффективное решение. Если ваша платформа позволяет, мы советуем перейти на использование встроенной функциональности 1С для работы с WebSocket. Это избавит вас от необходимости использования внешних компонент и предоставит полноценный событийный API.

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

← На главную