Как уведомить пользователя 1С о завершении фонового задания?

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

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

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

Почему функция Сообщить() не работает в фоновом задании?

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

Когда вы выполняете код на сервере, например, в общем модуле, который запускается в фоновом задании, этот код выполняется в отдельном сеансе на сервере, не связанном напрямую с активным клиентским сеансом пользователя. Функция Сообщить() предназначена для вывода информации в окно сообщений или статусную строку текущей активной формы клиентского приложения. Поскольку фоновое задание работает на сервере в отрыве от пользовательского интерфейса, у него нет "формы" или "клиента", куда можно было бы вывести сообщение. Поэтому вызовы Сообщить() из серверного фонового задания просто игнорируются или не имеют эффекта для пользователя.

Рассмотрим конкретный пример, который подтверждает это:


// Общий модуль (Сервер, ВнешнееСоединение, ВызовСервера)
Процедура СоздатьЭлементСправочникаСправочник() Экспорт
    СоздСпр = Справочники.Справочник.СоздатьЭлемент();
    СоздСпр.Наименование = Новый УникальныйИдентификатор;
    СоздСпр.Записать();
КонецПроцедуры

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

В этом случае сообщение "Сработало событие ПриЗаписи" не будет видно пользователю, работающему с 1С, так как оно генерируется в серверном контексте, вызванном фоновым заданием.

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

1. Получение сообщений из фонового задания с помощью обработчика ожидания

Один из базовых подходов заключается в том, чтобы фоновое задание явно записывало сообщения, а клиентское приложение периодически опрашивало сервер на предмет их наличия.

  1. Формирование сообщений в фоновом задании:

    Несмотря на то что Сообщить() не выводит сообщения напрямую пользователю, фоновое задание все же может формировать их. Эти сообщения сохраняются в специальном буфере, связанном с фоновым заданием. Нам достаточно вызвать Сообщить("Текст сообщения"); внутри серверной процедуры, выполняющейся в фоновом задании.

    
    // Процедура, выполняющаяся в фоновом задании
    Процедура МояДлительнаяОперацияВФоне() Экспорт
        // ... выполнение каких-то действий ...
        Сообщить("Этап 1: Начало выполнения операции.");
        // ... часть работы ...
        Сообщить("Этап 2: Обработано 100 из 500 элементов.");
        // ... завершение работы ...
        Сообщить("Операция успешно завершена!");
    КонецПроцедуры
    
  2. Получение сообщений на клиенте:

    После запуска фонового задания, клиентское приложение должно иметь возможность периодически запрашивать его статус и сообщения. Для этого мы используем объект ФоновоеЗадание и его метод ПолучитьСообщенияПользователю().

    Типичная реализация включает использование обработчика ожидания на клиенте:

    1. Запускаем фоновое задание с клиента.
    2. Открываем форму ожидания (или просто подключаем обработчик ожидания к текущей форме).
    3. В обработчике ожидания (который вызывается каждые несколько секунд) проверяем статус фонового задания и получаем его сообщения.
    4. Выводим полученные сообщения пользователю в клиентской форме.
    5. После завершения фонового задания отключаем обработчик ожидания и закрываем форму ожидания (если она была).
    
    // На клиенте
    Перем ИдентификаторФоновогоЗадания;
    Перем ОбработчикОжиданияУстановлен;
    
    &НаКлиенте
    Процедура ЗапуститьФоновоеЗадание(Команда)
        // Запускаем фоновое задание на сервере
        ИдентификаторФоновогоЗадания = ЗапуститьФоновоеЗаданиеНаСервере();
    
        Если ИдентификаторФоновогоЗадания <> Неопределено Тогда
            ПодключитьОбработчикОжидания("ПроверитьСтатусФоновогоЗадания", 5); // Проверять каждые 5 секунд
            ОбработчикОжиданияУстановлен = Истина;
            // Возможно, показать форму "Пожалуйста, подождите..."
        КонецЕсли;
    КонецПроцедуры
    
    &НаСервере
    Функция ЗапуститьФоновоеЗаданиеНаСервере()
        ФЗ = ФоновыеЗадания.Выполнить("ОбщийМодуль.МояДлительнаяОперацияВФоне", , "МояДлительнаяОперация");
        Возврат ФЗ.УникальныйИдентификатор;
    КонецФункции
    
    &НаКлиенте
    Процедура ПроверитьСтатусФоновогоЗадания()
        Если ИдентификаторФоновогоЗадания = Неопределено Тогда
            ОтключитьОбработчикОжидания("ПроверитьСтатусФоновогоЗадания");
            ОбработчикОжиданияУстановлен = Ложь;
            Возврат;
        КонецЕсли;
    
        // На сервере получаем статус и сообщения
        ДанныеФЗ = ПолучитьСтатусИСообщенияФЗНаСервере(ИдентификаторФоновогоЗадания);
    
        Если ДанныеФЗ.Статус = "Выполняется" Тогда
            // Выводим полученные сообщения
            Для Каждого Сообщение Из ДанныеФЗ.Сообщения Цикл
                Сообщить(Сообщение.Текст);
            КонецЦикла;
        ИначеЕсли ДанныеФЗ.Статус = "Завершено" ИЛИ ДанныеФЗ.Статус = "Ошибка" Тогда
            ОтключитьОбработчикОжидания("ПроверитьСтатусФоновогоЗадания");
            ОбработчикОжиданияУстановлен = Ложь;
            // Скрыть форму "Пожалуйста, подождите..."
            // Вывести итоговые сообщения
            Для Каждого Сообщение Из ДанныеФЗ.Сообщения Цикл
                Сообщить(Сообщение.Текст);
            КонецЦикла;
            Сообщить("Фоновое задание " + ДанныеФЗ.Статус);
            ИдентификаторФоновогоЗадания = Неопределено;
        КонецЕсли;
    КонецПроцедуры
    
    &НаСервереБезКонтекста
    Функция ПолучитьСтатусИСообщенияФЗНаСервере(ИдентификаторФЗ)
        ФоновоеЗаданиеОбъект = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ИдентификаторФЗ);
        Если ФоновоеЗаданиеОбъект = Неопределено Тогда
            Возврат Новый Структура("Статус, Сообщения", "НеНайдено", Новый Массив);
        КонецЕсли;
    
        Возврат Новый Структура("Статус, Сообщения", ФоновоеЗаданиеОбъект.Состояние, ФоновоеЗаданиеОбъект.ПолучитьСообщенияПользователю());
    КонецФункции
    

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

2. Использование подсистемы "Длительные операции" (БСП) или ее аналогов

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

Основные компоненты этого подхода:

  1. Серверная часть (`ДлительныеОперации.ВыполнитьВФоне`):

    На сервере используем функцию ДлительныеОперации.ВыполнитьВФоне(), которая запускает указанную процедуру/функцию в фоновом задании. Она также позволяет передавать параметры выполнения, такие как наименование и ключ фонового задания.

    
    // В общих модулях или модуле менеджера объекта
    Процедура МассовоеЗаполнениеДанных(Данные = Неопределено, АдресРезультата = Неопределено) Экспорт
        // Здесь мы можем отправлять уведомления в родительский сеанс
        // Это будет подробно рассмотрено в разделе про УведомленияКлиента
        // ...
        // Наше творчество по заполнению чего-то из чего-то
        Для Сч = 1 По 100 Цикл // Пример цикла для демонстрации прогресса
            // ... длительные вычисления или запись данных ...
            Если Сч % 10 = 0 Тогда
                // Отправляем промежуточное уведомление о прогрессе
                // Для примера, используем механизм УведомленияКлиента,
                // который часто используется совместно с ДлительнымиОперациями
                Если НомераРодительскогоСеанса.Количество > 0 Тогда
                    УведомленияКлиента.ОтправитьУведомление(
                        "Обновлялка", 
                        Новый Структура("Количество, Выполнено", 100, Сч), 
                        НомераРодительскогоСеанса
                    );
                КонецЕсли;
            КонецЕсли;
        КонецЦикла;
        // ... конец нашего творчества
        Сообщить("Массовое заполнение данных завершено!"); // Сообщение, которое можно будет получить
    
        Если НомераРодительскогоСеанса.Количество > 0 Тогда
             УведомленияКлиента.ОтправитьУведомление(
                "Обновлялка", 
                Новый Структура("Количество, Выполнено", 100, 100), 
                НомераРодительскогоСеанса
            );
        КонецЕсли;
    КонецПроцедуры
    
  2. Клиентская часть (`ДлительныеОперацииКлиент.ОжидатьЗавершение` и `УведомленияКлиента`):

    На клиенте для отслеживания хода выполнения и получения уведомлений мы используем методы БСП. В современном подходе часто применяется механизм УведомленияКлиента, который позволяет серверу активно отправлять информацию клиенту, вместо того чтобы клиент постоянно опрашивал сервер.

    Рассмотрим пример, который комбинирует запуск через ДлительныеОперации и получение уведомлений через УведомленияКлиента.

    
    // В модуле формы на клиенте
    Перем ПерваяЗапись;
    Перем ВремяНачала;
    
    &НаКлиенте
    Процедура Обновить(Команда)
        Если ОбновитьНаСервере() Тогда
            ПерваяЗапись = Истина;
            // Подключаем обработчик для получения уведомлений от сервера
            УведомленияКлиента.ПодключитьОбработчик("Обновлялка", Новый ОписаниеОповещения("ОбработкаОповещенияФоновогоЗадания", ЭтотОбъект));
        КонецЕсли;
    КонецПроцедуры
    
    &НаСервере
    Функция ОбновитьНаСервере()
        ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияВФоне;
        ПараметрыВыполнения.НаименованиеФоновогоЗадания = "Обновление данных";
        ПараметрыВыполнения.КлючФоновогоЗадания = "ОбновлениеДанных" + ОбщегоНазначенияКлиентСервер.ПолучитьИдентификаторТекущегоСеанса(); // Ключ для предотвращения повторного запуска
        ПараметрыВыполнения.ПолучатьСообщенияПользователю = Истина; // Важно для получения сообщений!
    
        // Определяем идентификаторы сеансов для отправки уведомлений
        НомераРодительскогоСеанса = Новый Массив;
        Попытка
            // Если задание запущено из текущего сеанса, получаем его номер
            НомерРодительскогоСеанса = ПолучитьТекущийСеансИнформационнойБазы().ПолучитьФоновоеЗадание().НомерРодительскогоСеанса;
            НомераРодительскогоСеанса.Добавить(НомерРодительскогоСеанса);
        Исключение
            // Если это не фоновое задание, а обычный вызов с клиента
            НомераРодительскогоСеанса.Добавить(ПолучитьТекущийСеансИнформационнойБазы().НомерСеанса);
        КонецПопытки;
    
        // Здесь мы передаем номера сеансов в процедуру, которая будет выполняться в фоне
        // Это позволит фоновому заданию отправлять уведомления именно этому сеансу.
        // При отсутствии БСП, пришлось бы передавать идентификатор текущего сеанса
        // в качестве параметра для фонового задания.
        СтруктураПараметров = Новый Структура("НомераСеансовДляУведомления", НомераРодительскогоСеанса);
    
        Результат = ДлительныеОперации.ВыполнитьВФоне(
            "РегистрыСведений.ДанныеДанных.МассовоеЗаполнениеДанных", // Или ваш общий модуль/процедура
            СтруктураПараметров, // Передаем параметры
            ПараметрыВыполнения
        );
    
        Если Результат.Статус = "Выполняется" Тогда
            // Элементы.ФормаОбновить.Доступность = Ложь; // Заблокировать кнопку на время выполнения
            Возврат Истина;
        Иначе
            // Обработка ошибок или немедленного завершения
            Возврат Ложь;
        КонецЕсли;
    КонецФункции
    
    &НаКлиенте
    Процедура ОбработкаОповещенияФоновогоЗадания(Данные, ДопПараметры) Экспорт
        // Данные - это структура, которую мы отправили из фонового задания
        // Например, Данные.Количество и Данные.Выполнено для прогресса
    
        // Обновление индикатора прогресса
        // Если у нас есть элемент формы "Прогресс"
        Если Элементы.Найти("Прогресс") <> Неопределено Тогда
            Элементы.Прогресс.МаксимальноеЗначение = Данные.Количество;
            Элементы.Прогресс.Значение = Данные.Выполнено;
        КонецЕсли;
    
        // Расчет оставшегося времени (пример)
        Если ПерваяЗапись Тогда
            ВремяНачала = ТекущаяУниверсальнаяДатаВМиллисекундах();
            ПерваяЗапись = Ложь;
        ИначеЕсли Данные.Выполнено > 0 Тогда
            ВремяНаЕдиницу = (ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачала) / Данные.Выполнено;
            Осталось = СтрШаблон("%1 с (%2 из %3)", Окр(ВремяНаЕдиницу * (Данные.Количество - Данные.Выполнено) / 1000, 1), Данные.Выполнено, Данные.Количество);
            Сообщить("Осталось: " + Осталось);
        КонецЕсли;
    
        Если Данные.Выполнено = Данные.Количество Тогда
            Сообщить("Задание завершено!");
            УведомленияКлиента.ОтключитьОбработчик("Обновлялка", Новый ОписаниеОповещения("ОбработкаОповещенияФоновогоЗадания", ЭтотОбъект));
            // Элементы.ФормаОбновить.Доступность = Истина; // Разблокировать кнопку
        КонецЕсли;
    КонецПроцедуры
    

    Этот подход с использованием ДлительныеОперации и УведомленияКлиента является наиболее рекомендуемым для длительных операций, так как он стандартизирован, позволяет отображать прогресс и не блокирует клиентский интерфейс, при этом клиент не должен постоянно опрашивать сервер.

3. Новый механизм уведомлений платформы 8.3.26+ (УведомленияКлиента)

Начиная с версии платформы 8.3.26, 1С предоставляет значительно улучшенный и более простой механизм для отправки уведомлений с сервера в клиентские сеансы. Этот механизм реализован через общий модуль УведомленияКлиента и позволяет отправлять уведомления напрямую, без постоянного опроса со стороны клиента и без необходимости подключения к Системе Взаимодействия (СВ).

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

  1. Отправка уведомления с сервера:

    В серверном коде (например, в фоновом задании) мы используем метод УведомленияКлиента.ОтправитьУведомление(). Этот метод позволяет указать:

    • Имя уведомления (Имя): Уникальная строка, по которой клиент будет идентифицировать уведомление.
    • Данные (Данные): Произвольные данные (например, структура), которые мы хотим передать клиенту (статус, прогресс, результат).
    • Адресаты (Адресаты): Массив номеров клиентских сеансов, которым нужно отправить уведомление. Если не указан, уведомление отправляется всем активным сеансам.
    • Время жизни (ВремяЖизни): Опционально, в секундах, как долго уведомление будет храниться на сервере, если клиент не успеет его получить.
    
    // В серверной процедуре (фонового задания)
    Процедура МояДлительнаяОперацияСУведомлениями(НомераСеансовДляУведомления) Экспорт
        Попытка
            // Здесь НомераСеансовДляУведомления - это массив, переданный из клиентского сеанса
            // (смотрите пример с ДлительныеОперации выше)
    
            Для Инд = 1 По 100 Цикл
                // ... выполнение части работы ...
                // Отправляем прогресс
                УведомленияКлиента.ОтправитьУведомление(
                    "ПрогрессВыполненияДлительнойОперации",
                    Новый Структура("ПроцентВыполнения", Инд, "Состояние", "Выполняется"),
                    НомераСеансовДляУведомления
                );
                Засыпать(100); // Имитация длительной работы
            КонецЦикла;
    
            // Отправляем уведомление о завершении
            УведомленияКлиента.ОтправитьУведомление(
                "ПрогрессВыполненияДлительнойОперации",
                Новый Структура("ПроцентВыполнения", 100, "Состояние", "Завершено", "Результат", "Успешно"),
                НомераСеансовДляУведомления
            );
        Исключение
            // Отправляем уведомление об ошибке
            УведомленияКлиента.ОтправитьУведомление(
                "ПрогрессВыполненияДлительнойОперации",
                Новый Структура("ПроцентВыполнения", 0, "Состояние", "Ошибка", "ТекстОшибки", ОписаниеОшибки()),
                НомераСеансовДляУведомления
            );
        КонецПопытки;
    КонецПроцедуры
    
  2. Получение уведомления на клиенте:

    На клиенте мы подписываемся на уведомления с определенным именем с помощью УведомленияКлиента.ПодключитьОбработчик().

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

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

4. Использование Системы взаимодействия (СВ)

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

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

  1. Настройка СВ:

    Для работы с СВ необходимо ее настроить: либо подключиться к облачному сервису 1cDialog.com, либо развернуть собственный сервер взаимодействия.

  2. Отправка сообщения из фонового задания в СВ:

    В серверном коде фонового задания мы можем использовать методы работы с Системой взаимодействия для отправки сообщения в определенное обсуждение. Это может быть системное обсуждение или обсуждение, связанное с каким-либо объектом.

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

    Пользователи, подписанные на соответствующее обсуждение в СВ, будут получать уведомления. Эти уведомления могут отображаться в панели Системы взаимодействия 1С, а также могут быть интегрированы с внешними мессенджерами (например, Telegram), если настроены соответствующие интеграции.

Преимущества СВ: Гарантированная доставка сообщений, возможность работы с уведомлениями даже если форма, запустившая фоновое задание, уже закрыта, интеграция с внешними сервисами, централизованное хранение истории сообщений. Недостатки СВ: Требует дополнительной настройки и лицензирования (для облачного сервиса), а также может быть избыточным для простых задач уведомления о прогрессе.

Общие рекомендации и важные моменты

  1. Используйте Ключ для фоновых заданий: При запуске фоновых заданий всегда указывайте КлючФоновогоЗадания. Это позволяет избежать повторного запуска одного и того же задания и легко находить его для проверки статуса. Например, ключ можно формировать с учетом параметров задания или текущего пользователя.
  2. Планируйте длительные операции: Любые операции, выполнение которых может занимать более 5-8 секунд, рекомендуется выносить в фоновые задания, чтобы не блокировать интерфейс пользователя и избежать таймаутов на веб-клиенте.
  3. Режим совместимости: Если вы переходите на новую версию платформы (например, 8.3.26), используйте режим совместимости для постепенного перехода и тестирования новых механизмов уведомлений.
  4. Сохранение результатов: Фоновое задание может возвращать результат. Если результат небольшой, его можно вернуть напрямую. Для больших результатов рекомендуется сохранять их во временное хранилище (например, ПоместитьВоВременноеХранилище()) и передавать клиенту адрес.

Выбор конкретного метода оповещения зависит от ваших требований к функциональности, версии платформы, наличия БСП в конфигурации и необходимости интеграции с другими системами. Для большинства случаев, особенно начиная с версии 8.3.26+, комбинация ДлительныеОперации (если используется БСП) и УведомленияКлиента предоставляет наиболее оптимальное и современное решение.

← На главную