Как реализовать асинхронные оповещения в управляемых формах 1С от HTTP-сервиса, выполняющего длительные операции?

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

При разработке информационных систем на платформе 1С:Предприятие 8.3, особенно при использовании управляемых форм, мы часто сталкиваемся с необходимостью выполнения длительных операций. Если такая операция вызывается напрямую из клиентского приложения (например, из формы), пользовательский интерфейс может зависнуть, что приводит к негативному опыту взаимодействия с программой. Эта проблема становится особенно актуальной, когда длительные операции инициируются через HTTP-сервисы, для которых важна быстрая организация и расширение API с использованием универсальных модулей — поможет мониторинг и анализ производительности HTTP-запросов.

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

Понимание проблемы: длительные операции и блокировка интерфейса

Когда мы выполняем вызов серверной процедуры из клиентского кода в 1С (будь то напрямую или через HTTP-сервис), клиентское приложение ожидает ответа от сервера. Если серверная операция занимает значительное время (например, несколько секунд или минут), клиентский интерфейс становится неактивным, пользователь не может взаимодействовать с формой. Это происходит потому, что вызов выполняется синхронно, в основном потоке выполнения клиентского приложения.

Для решения этой проблемы нам необходимо перестроить архитектуру взаимодействия так, чтобы длительная операция выполнялась в фоновом режиме на сервере, а клиент при этом мог продолжать работу и периодически проверять статус этой операции или получать уведомления о её завершении. Часто такая потребность возникает, когда реализуется интеграция между 1С без боли и страданий с использованием JSON и REST-архитектуры.

Решение 1: Асинхронное выполнение через фоновые задания и клиентский опрос

Это наиболее распространенный и надежный способ реализации асинхронных операций в 1С. В общем случае мы разделяем процесс на две основные части:

  1. Серверная часть: HTTP-сервис не выполняет долгую операцию напрямую, а лишь инициирует её выполнение в отдельном фоновом задании и немедленно возвращает клиенту идентификатор этого задания.
  2. Клиентская часть: Клиентское приложение запускает обработчик ожидания, который периодически опрашивает сервер о статусе фонового задания, используя его идентификатор.

Шаг 1: Реализация серверной логики с фоновыми заданиями

На стороне сервера нам потребуется модуль, который будет содержать две ключевые функции или процедуры:

Рассмотрим пример кода для серверного модуля. Допустим, у нас есть общий модуль МодульСервера с установленным флагом "Сервер".

Запуск фонового задания из HTTP-сервиса

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

Мы рассмотрим пример, где HTTP-сервис вызывает серверную функцию ВыполнитьДолгуюОперациюИВернутьID. Она, в свою очередь, запускает фоновое задание.

Пример кода для серверного модуля (например, в общем модуле с флагом "Сервер"):


// В общем модуле "МодульСервера" (с установленным флагом "Сервер")

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

    Попытка
        // Запускаем фоновое задание.
        ФоновоеЗадание = ФоновыеЗадания.Выполнить("МодульСервера.ДолгаяОперацияВФоне", ПараметрыФоновогоЗадания, , "Длительная операция из HTTP-сервиса");

        // Если фоновое задание успешно запущено, возвращаем его уникальный идентификатор.
        Возврат ФоновоеЗадание.УникальныйИдентификатор;
    Исключение
        ЗаписатьСообщениеВЖурналРегистрации(
            "Ошибка при запуске фонового задания: " + ОписаниеОшибки(),
            УровеньВажностиЖурналаСобытий.Ошибка,
            ,,
            "ЗапускДолгойОперации"
        );
        ВызватьИсключение "Не удалось запустить фоновое задание: " + ОписаниеОшибки();
    КонецПопытки;

КонецФункции

// Процедура, которая выполняется в фоновом задании.
Процедура ДолгаяОперацияВФоне(Параметры) Экспорт
    ЗаписатьСообщениеВЖурналРегистрации("Фоновое задание начато для ключа: " + Параметры.КлючОперации, УровеньВажностиЖурналаСобытий.Информация);

    Для Инд = 1 По 10 Цикл 
        Попытка
            ЗаписатьСообщениеВЖурналРегистрации(
                "Шаг " + Инд + " выполнен. Ключ: " + Параметры.КлючОперации + ". Пользователь: " + Параметры.ИмяПользователя,
                УровеньВажностиЖурналаСобытий.Информация,
                ,,
                "ВыполнениеДолгойОперации"
            );
            ОбщегоНазначенияКлиентСервер.Пауза(1); 
            
            Если Инд = 7 Тогда
                 // ВызватьИсключение "Принудительная ошибка на шаге " + Инд;
            КонецЕсли;

        Исключение
            ЗаписатьСообщениеВЖурналРегистрации(
                "Ошибка на шаге " + Инд + " фонового задания для ключа: " + Параметры.КлючОперации + ". Описание: " + ОписаниеОшибки(),
                УровеньВажностиЖурналаСобытий.Ошибка,
                ,,
                "ОшибкаДолгойОперации"
            );
            ВызватьИсключение; 
        КонецПопытки;
    КонецЦикла;

    ЗаписатьСообщениеВЖурналРегистрации("Фоновое задание завершено для ключа: " + Параметры.КлючОперации, УровеньВажностиЖурналаСобытий.Информация);
КонецПроцедуры

// Функция для получения текущего статуса фонового задания по его УникальномуИдентификатору.
Функция ПолучитьСтатусФоновогоЗадания(УИДФоновогоЗадания) Экспорт
    Попытка
        ФЗ = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(УИДФоновогоЗадания);

        Если ФЗ = Неопределено Тогда
            Возврат "Не найдено";
        Иначе
            Состояние = ФЗ.Состояние;
            Если Состояние = СостояниеФоновогоЗадания.Выполнено Тогда
                Возврат "Выполнено";
            ИначеЕсли Состояние = СостояниеФоновогоЗадания.Ошибка Тогда
                Возврат "Ошибка";
            ИначеЕсли Состояние = СостояниеФоновогоЗадания.Выполняется Тогда
                Возврат "В работе";
            Иначе
                Возврат "Ожидает"; 
            КонецЕсли;
        КонецЕсли;
    Исключение
        ЗаписатьСообщениеВЖурналРегистрации(
            "Ошибка при получении статуса фонового задания (УИД: " + УИДФоновогоЗадания + "): " + ОписаниеОшибки(),
            УровеньВажностиЖурналаСобытий.Ошибка
        );
        Возврат "Ошибка запроса статуса";
    КонецПопытки;
КонецФункции

Важные моменты при работе с фоновыми заданиями:

  1. Передача параметров: Все параметры должны быть сериализуемыми.
  2. Область видимости: Процедура должна быть экспортируемой в общем модуле.
  3. Мониторинг: Используйте консоль фоновых заданий (поможет инструментарий разработчика для управления фоновыми заданиями) и журнал регистрации.

Шаг 2: Реализация клиентской логики с обработчиком ожидания

В управляемой форме нам потребуется реализовать мониторинг фоновых заданий с помощью обработчика ожидания — для этого подойдёт мониторинг фоновых заданий и оповещения в Telegram. Стоит отметить, что в некоторых случаях вместо периодического опроса удобнее использовать управление WebSocket-соединениями или специализированный REST API To WebSocket прокси сервис для получения мгновенных уведомлений.

Пример кода для модуля управляемой формы:


// В модуле управляемой формы
Перем ИдентификаторТекущегоФоновогоЗадания Экспорт; 

&НаКлиенте
Процедура НачатьДлительнуюОперацию(Команда)
    Если ИдентификаторТекущегоФоновогоЗадания <> Неопределено Тогда
        Сообщить("Уже выполняется длительная операция. Дождитесь её завершения.", СтатусСообщения.Важное);
        Возврат;
    КонецЕсли;

    Сообщить("Запуск длительной операции. Пожалуйста, ожидайте уведомления...", СтатусСообщения.Информация);

    Попытка
        ПараметрыДляЗапуска = Новый Структура();
        ИдентификаторТекущегоФоновогоЗадания = МодульСервера.ВыполнитьДолгуюОперациюИВернутьID(ПараметрыДляЗапуска);

        Если ИдентификаторТекущегоФоновогоЗадания <> Неопределено Тогда
            ПодключитьОбработчикОжидания("ПроверитьСтатусОперации", 1, Истина);
            Сообщить("Операция запущена. Идентификатор задания: " + ИдентификаторТекущегоФоновогоЗадания, СтатусСообщения.Информация);
        Иначе
            Сообщить("Не удалось запустить операцию. Подробности в журнале регистрации.", СтатусСообщения.Ошибка);
            ИдентификаторТекущегоФоновогоЗадания = Неопределено; 
        КонецЕсли;

    Исключение
        Сообщить("Ошибка при инициировании операции: " + ОписаниеОшибки(), СтатусСообщения.Ошибка);
        ИдентификаторТекущегоФоновогоЗадания = Неопределено;
    КонецПопытки;
КонецПроцедуры

&НаКлиенте
Процедура ПроверитьСтатусОперации()
    Если ИдентификаторТекущегоФоновогоЗадания = Неопределено Тогда
        ОтключитьОбработчикОжидания("ПроверитьСтатусОперации");
        Возврат;
    КонецЕсли;

    Статус = МодульСервера.ПолучитьСтатусФоновогоЗадания(ИдентификаторТекущегоФоновогоЗадания);

    Если Статус = "Выполнено" Тогда
        Сообщить("Длительная операция успешно завершена!", СтатусСообщения.Важное);
        ОтключитьОбработчикОжидания("ПроверитьСтатусОперации");
        ИдентификаторТекущегоФоновогоЗадания = Неопределено; 
    ИначеЕсли Статус = "Ошибка" Тогда
        Сообщить("Длительная операция завершилась с ошибкой! Подробности в журнале регистрации.", СтатусСообщения.Ошибка);
        ОтключитьОбработчикОжидания("ПроверитьСтатусОперации");
        ИдентификаторТекущегоФоновогоЗадания = Неопределено;
    ИначеЕсли Статус = "В работе" Тогда
        Сообщить("Операция продолжает выполняться...", СтатусСообщения.Информация, , , Истина); 
    ИначеЕсли Статус = "Не найдено" Тогда
        Сообщить("Ошибка: Фоновое задание не найдено.", СтатусСообщения.Ошибка);
        ОтключитьОбрабочикОжидания("ПроверитьСтатусОперации");
        ИдентификаторТекущегоФоновогоЗадания = Неопределено;
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура ПриЗакрытии()
    ОтключитьОбработчикОжидания("ПроверитьСтатусОперации");
КонецПроцедуры

Решение 2: Использование ОповеститьПользователей и системных уведомлений

Механизм ОповеститьПользователей позволяет выполнять оповещение пользователей при завершении серверных процедур. Если же вам требуется более тесная интеграция с ОС, можно реализовать уведомление из 1С:Предприятия непосредственно в центре уведомлений Windows.

Пример использования ОповеститьПользователей:


// В процедуре ДолгаяОперацияВФоне(Параметры) Экспорт после завершения работы:

ОписаниеОповещения = Новый ОписаниеОповещения(
    "ОбработатьОповещениеОЗавершении",
    Параметры.ИдентификаторОповещения 
);

РезультатОперации = Новый Структура("Статус, Сообщение", "Завершено", "Данные успешно обработаны.");

ПользовательДляОповещения = Пользователи.НайтиПоИмени(Параметры.ИмяПользователя); 
Если ПользовательДляОповещения <> Неопределено Тогда
    ОповеститьПользователей(ОписаниеОповещения, РезультатОперации, ПользовательДляОповещения);
КонецЕсли;

Клиентская часть (в модуле управляемой формы):


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

Дополнительные рекомендации и лучшие практики

  1. Интерфейс ожидания: Используйте индикаторы выполнения для улучшения UX.
  2. Ограничение частоты опроса: Интервал в 1-5 секунд обычно оптимален.
  3. Логирование: Обязательно фиксируйте ход выполнения в журнале регистрации.
  4. Прогресс выполнения: Можно передавать процент завершения через временное хранилище.
  5. Таймауты: Убедитесь, что настройки HTTP-сервиса позволяют корректно запустить фоновый процесс.

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

← На главную