При разработке информационных систем на платформе 1С:Предприятие 8.3, особенно при использовании управляемых форм, мы часто сталкиваемся с необходимостью выполнения длительных операций. Если такая операция вызывается напрямую из клиентского приложения (например, из формы), пользовательский интерфейс может зависнуть, что приводит к негативному опыту взаимодействия с программой. Эта проблема становится особенно актуальной, когда длительные операции инициируются через HTTP-сервисы, для которых важна быстрая организация и расширение API с использованием универсальных модулей — поможет мониторинг и анализ производительности HTTP-запросов.
Рассмотрим, как мы можем эффективно решить эту задачу, обеспечив асинхронное выполнение операций и своевременное оповещение пользователя о их завершении или статусе, не блокируя при этом работу клиентского приложения. Мы проанализируем различные подходы, включая асинхронные вызовы и паттерны выполнения длительных операций, использование фоновых заданий, обработчиков ожидания и, в некоторых случаях, механизма оповещения пользователей.
Когда мы выполняем вызов серверной процедуры из клиентского кода в 1С (будь то напрямую или через HTTP-сервис), клиентское приложение ожидает ответа от сервера. Если серверная операция занимает значительное время (например, несколько секунд или минут), клиентский интерфейс становится неактивным, пользователь не может взаимодействовать с формой. Это происходит потому, что вызов выполняется синхронно, в основном потоке выполнения клиентского приложения.
Для решения этой проблемы нам необходимо перестроить архитектуру взаимодействия так, чтобы длительная операция выполнялась в фоновом режиме на сервере, а клиент при этом мог продолжать работу и периодически проверять статус этой операции или получать уведомления о её завершении. Часто такая потребность возникает, когда реализуется интеграция между 1С без боли и страданий с использованием JSON и REST-архитектуры.
Это наиболее распространенный и надежный способ реализации асинхронных операций в 1С. В общем случае мы разделяем процесс на две основные части:
На стороне сервера нам потребуется модуль, который будет содержать две ключевые функции или процедуры:
Рассмотрим пример кода для серверного модуля. Допустим, у нас есть общий модуль МодульСервера с установленным флагом "Сервер".
Прежде всего, нам нужно создать HTTP-сервис, который будет принимать запросы от клиента. Вместо непосредственного выполнения длительной операции, этот сервис должен запускать фоновое задание и возвращать клиенту его уникальный идентификатор. Этот идентификатор будет служить "ключом" для отслеживания статуса операции.
Мы рассмотрим пример, где HTTP-сервис вызывает серверную функцию ВыполнитьДолгуюОперациюИВернутьID. Она, в свою очередь, запускает фоновое задание.
Пример кода для серверного модуля (например, в общем модуле с флагом "Сервер"):
// В общем модуле "МодульСервера" (с установленным флагом "Сервер")
// Функция, которая будет вызываться HTTP-сервисом или напрямую с клиента (с вызовом на сервер)
// Она запускает фоновое задание и возвращает его УникальныйИдентификатор
Функция ВыполнитьДолгуюОперациюИВернутьID(ПараметрыОперации) Экспорт
// Создадим уникальный ключ для текущей операции.
ПараметрыФоновогоЗадания = Новый Структура();
ПараметрыФоновогоЗадания.Вставить("КлючОперации", Новый УникальныйИдентификатор());
ПараметрыФоновогоЗадания.Вставить("ИмяПользователя", Пользователи.ТекущийПользователь().Имя);
Попытка
// Запускаем фоновое задание.
ФоновоеЗадание = ФоновыеЗадания.Выполнить("МодульСервера.ДолгаяОперацияВФоне", ПараметрыФоновогоЗадания, , "Длительная операция из HTTP-сервиса");
// Если фоновое задание успешно запущено, возвращаем его уникальный идентификатор.
Возврат ФоновоеЗадание.УникальныйИдентификатор;
Исключение
ЗаписатьСообщениеВЖурналРегистрации(
"Ошибка при запуске фонового задания: " + ОписаниеОшибки(),
УровеньВажностиЖурналаСобытий.Ошибка,
,,
"ЗапускДолгойОперации"
);
ВызватьИсключение "Не удалось запустить фоновое задание: " + ОписаниеОшибки();
КонецПопытки;
КонецФункции
// Процедура, которая выполняется в фоновом задании.
Процедура ДолгаяОперацияВФоне(Параметры) Экспорт
ЗаписатьСообщениеВЖурналРегистрации("Фоновое задание начато для ключа: " + Параметры.КлючОперации, УровеньВажностиЖурналаСобытий.Информация);
Для Инд = 1 По 10 Цикл
Попытка
ЗаписатьСообщениеВЖурналРегистрации(
"Шаг " + Инд + " выполнен. Ключ: " + Параметры.КлючОперации + ". Пользователь: " + Параметры.ИмяПользователя,
УровеньВажностиЖурналаСобытий.Информация,
,,
"ВыполнениеДолгойОперации"
);
ОбщегоНазначенияКлиентСервер.Пауза(1);
Если Инд = 7 Тогда
// ВызватьИсключение "Принудительная ошибка на шаге " + Инд;
КонецЕсли;
Исключение
ЗаписатьСообщениеВЖурналРегистрации(
"Ошибка на шаге " + Инд + " фонового задания для ключа: " + Параметры.КлючОперации + ". Описание: " + ОписаниеОшибки(),
УровеньВажностиЖурналаСобытий.Ошибка,
,,
"ОшибкаДолгойОперации"
);
ВызватьИсключение;
КонецПопытки;
КонецЦикла;
ЗаписатьСообщениеВЖурналРегистрации("Фоновое задание завершено для ключа: " + Параметры.КлючОперации, УровеньВажностиЖурналаСобытий.Информация);
КонецПроцедуры
// Функция для получения текущего статуса фонового задания по его УникальномуИдентификатору.
Функция ПолучитьСтатусФоновогоЗадания(УИДФоновогоЗадания) Экспорт
Попытка
ФЗ = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(УИДФоновогоЗадания);
Если ФЗ = Неопределено Тогда
Возврат "Не найдено";
Иначе
Состояние = ФЗ.Состояние;
Если Состояние = СостояниеФоновогоЗадания.Выполнено Тогда
Возврат "Выполнено";
ИначеЕсли Состояние = СостояниеФоновогоЗадания.Ошибка Тогда
Возврат "Ошибка";
ИначеЕсли Состояние = СостояниеФоновогоЗадания.Выполняется Тогда
Возврат "В работе";
Иначе
Возврат "Ожидает";
КонецЕсли;
КонецЕсли;
Исключение
ЗаписатьСообщениеВЖурналРегистрации(
"Ошибка при получении статуса фонового задания (УИД: " + УИДФоновогоЗадания + "): " + ОписаниеОшибки(),
УровеньВажностиЖурналаСобытий.Ошибка
);
Возврат "Ошибка запроса статуса";
КонецПопытки;
КонецФункции
Важные моменты при работе с фоновыми заданиями:
В управляемой форме нам потребуется реализовать мониторинг фоновых заданий с помощью обработчика ожидания — для этого подойдёт мониторинг фоновых заданий и оповещения в Telegram. Стоит отметить, что в некоторых случаях вместо периодического опроса удобнее использовать управление WebSocket-соединениями или специализированный REST API To WebSocket прокси сервис для получения мгновенных уведомлений.
Пример кода для модуля управляемой формы:
// В модуле управляемой формы
Перем ИдентификаторТекущегоФоновогоЗадания Экспорт;
&НаКлиенте
Процедура НачатьДлительнуюОперацию(Команда)
Если ИдентификаторТекущегоФоновогоЗадания <> Неопределено Тогда
Сообщить("Уже выполняется длительная операция. Дождитесь её завершения.", СтатусСообщения.Важное);
Возврат;
КонецЕсли;
Сообщить("Запуск длительной операции. Пожалуйста, ожидайте уведомления...", СтатусСообщения.Информация);
Попытка
ПараметрыДляЗапуска = Новый Структура();
ИдентификаторТекущегоФоновогоЗадания = МодульСервера.ВыполнитьДолгуюОперациюИВернутьID(ПараметрыДляЗапуска);
Если ИдентификаторТекущегоФоновогоЗадания <> Неопределено Тогда
ПодключитьОбработчикОжидания("ПроверитьСтатусОперации", 1, Истина);
Сообщить("Операция запущена. Идентификатор задания: " + ИдентификаторТекущегоФоновогоЗадания, СтатусСообщения.Информация);
Иначе
Сообщить("Не удалось запустить операцию. Подробности в журнале регистрации.", СтатусСообщения.Ошибка);
ИдентификаторТекущегоФоновогоЗадания = Неопределено;
КонецЕсли;
Исключение
Сообщить("Ошибка при инициировании операции: " + ОписаниеОшибки(), СтатусСообщения.Ошибка);
ИдентификаторТекущегоФоновогоЗадания = Неопределено;
КонецПопытки;
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьСтатусОперации()
Если ИдентификаторТекущегоФоновогоЗадания = Неопределено Тогда
ОтключитьОбработчикОжидания("ПроверитьСтатусОперации");
Возврат;
КонецЕсли;
Статус = МодульСервера.ПолучитьСтатусФоновогоЗадания(ИдентификаторТекущегоФоновогоЗадания);
Если Статус = "Выполнено" Тогда
Сообщить("Длительная операция успешно завершена!", СтатусСообщения.Важное);
ОтключитьОбработчикОжидания("ПроверитьСтатусОперации");
ИдентификаторТекущегоФоновогоЗадания = Неопределено;
ИначеЕсли Статус = "Ошибка" Тогда
Сообщить("Длительная операция завершилась с ошибкой! Подробности в журнале регистрации.", СтатусСообщения.Ошибка);
ОтключитьОбработчикОжидания("ПроверитьСтатусОперации");
ИдентификаторТекущегоФоновогоЗадания = Неопределено;
ИначеЕсли Статус = "В работе" Тогда
Сообщить("Операция продолжает выполняться...", СтатусСообщения.Информация, , , Истина);
ИначеЕсли Статус = "Не найдено" Тогда
Сообщить("Ошибка: Фоновое задание не найдено.", СтатусСообщения.Ошибка);
ОтключитьОбрабочикОжидания("ПроверитьСтатусОперации");
ИдентификаторТекущегоФоновогоЗадания = Неопределено;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПриЗакрытии()
ОтключитьОбработчикОжидания("ПроверитьСтатусОперации");
КонецПроцедуры
Механизм ОповеститьПользователей позволяет выполнять оповещение пользователей при завершении серверных процедур. Если же вам требуется более тесная интеграция с ОС, можно реализовать уведомление из 1С:Предприятия непосредственно в центре уведомлений Windows.
Пример использования ОповеститьПользователей:
// В процедуре ДолгаяОперацияВФоне(Параметры) Экспорт после завершения работы:
ОписаниеОповещения = Новый ОписаниеОповещения(
"ОбработатьОповещениеОЗавершении",
Параметры.ИдентификаторОповещения
);
РезультатОперации = Новый Структура("Статус, Сообщение", "Завершено", "Данные успешно обработаны.");
ПользовательДляОповещения = Пользователи.НайтиПоИмени(Параметры.ИмяПользователя);
Если ПользовательДляОповещения <> Неопределено Тогда
ОповеститьПользователей(ОписаниеОповещения, РезультатОперации, ПользовательДляОповещения);
КонецЕсли;
Клиентская часть (в модуле управляемой формы):
&НаКлиенте
Процедура ОбработатьОповещениеОЗавершении(Результат, ДополнительныеПараметры) Экспорт
Если ТипЗнч(Результат) = Тип("Структура") Тогда
Сообщить("Получено оповещение от сервера: " + Результат.Статус, СтатусСообщения.Важное);
КонецЕсли;
КонецПроцедуры
Мы рассмотрели детальный подход к реализации асинхронных оповещений. Использование этих методик в сочетании с современными инструментами, такими как библиотеки для создания конвейеров задач, позволяет создавать высокопроизводительные и отзывчивые решения на платформе 1С:Предприятие.