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

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

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

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

Подход 1: Использование механизма оповещений через СообщитьПользователю

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

Принцип работы:

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

Пошаговая реализация:

Шаг 1: Запуск фонового задания на сервере и сохранение результата

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


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

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

Обратите внимание, что мы используем ПоместитьВоВременноеХранилище() для сохранения результата. Это позволяет передавать сложные структуры данных между серверным и клиентским контекстами, а также между различными сеансами, если это необходимо. Важно передать уникальный идентификатор для временного хранилища, чтобы избежать коллизий. Кроме того, при использовании ключей заданий стоит учитывать неочевидную особенность фоновых заданий, чтобы избежать неожиданного поведения платформы при поиске активных заданий.

Шаг 2: Запуск фонового задания на клиенте и подписка на оповещения

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


// В модуле формы (клиент)
Перем мИдентификаторЗадания; // Для хранения идентификатора запущенного задания

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

// В модуле формы или в общем модуле (сервер, вызывается с клиента)
Процедура ЗапускФоновогоЗаданияНаСервере(ИдентификаторЗадания, ИмяФайла) Экспорт
    ЗапускФоновогоЗадания(ИмяФайла, ИдентификаторЗадания); // Вызов ранее определенной процедуры
КонецПроцедуры

Метод ПодключитьОбработчикОповещения() принимает имя процедуры-обработчика и контекст (обычно ЭтаФорма). Эта процедура будет вызвана автоматически, когда поступит оповещение от сервера.

Шаг 3: Обработка оповещения на клиенте

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


// В модуле формы (клиент)
&НаКлиенте
Процедура ОбработатьЗавершениеЗагрузки(Идентификатор, АдресВХСРезультатом) Экспорт
    // Отключаем обработчик, чтобы он не вызывался повторно
    ОтключитьОбработчикОповещения("ОбработатьЗавершениеЗагрузки");
    
    // Идентификатор в данном случае будет текстом сообщения, 
    // если мы не передавали ИдентификаторОбъекта
    Если Идентификатор = "Загрузка данных завершена." Тогда 
        // Получаем результат из временного хранилища
        Результат = ПолучитьИзВременногоХранилища(АдресВХСРезультатом);
        
        Если Результат <> Неопределено Тогда
            Сообщить("Фоновое задание завершено. Загружено строк: " + Результат.КоличествоЗагруженныхСтрок);
            Если Результат.Ошибки.Количество() > 0 Тогда
                Сообщить("В ходе загрузки возникли ошибки:");
                Для Каждого Ошибка Из Результат.Ошибки Цикл
                    Сообщить("- " + Ошибка);
                КонецЦикла;
            КонецЕсли;
        Иначе
            Сообщить("Фоновое задание завершено, но результат не был получен.");
        КонецЕсли;
    КонецЕсли;
    
    мИдентификаторЗадания = Неопределено; // Сбросим идентификатор
КонецПроцедуры

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

Подход 2: Использование обработчика ожидания и периодической проверки статуса фонового задания

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

Принцип работы:

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

Пошаговая реализация:

Шаг 1: Запуск фонового задания на сервере

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


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

// Процедура для запуска фонового задания, вызываемая с клиента
Процедура ЗапуститьФоновоеЗаданиеНаСервере(ИдентификаторЗадания, ИмяФайла) Экспорт
    МассивПараметров = Новый Массив();
    МассивПараметров.Добавить(ИмяФайла);
    
    // Запускаем фоновое задание
    ФоновыеЗадания.Выполнить("МояОбработка.ДлительнаяОперация", // Имя функции в модуле объекта обработки
        МассивПараметров, 
        ИдентификаторЗадания, 
        "Загрузка из файла " + ИмяФайла);
КонецПроцедуры

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

Шаг 2: Запуск фонового задания на клиенте и подключение обработчика ожидания

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


// В модуле формы (клиент)
Перем мИдентификаторФоновогоЗадания Экспорт; // Храним идентификатор фонового задания

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

    // Генерируем уникальный идентификатор для фонового задания
    мИдентификаторФоновогоЗадания = УникальныйИдентификатор();

    // Вызываем серверную процедуру для запуска фонового задания
    ЗапуститьФоновоеЗаданиеНаСервере(мИдентификаторФоновогоЗадания, "МойФайл.csv");

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

Мы используем УникальныйИдентификатор() для создания уникального ключа задания. Это гарантирует, что мы будем отслеживать именно наше задание. Интервал в 1 секунду — это разумное значение для большинства случаев, но его можно изменить в зависимости от ожидаемой длительности операции и требуемой "отзывчивости" интерфейса.

Шаг 3: Проверка статуса и получение результата в обработчике ожидания

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


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

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

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

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

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

Важнейший момент здесь – использование ФоновыеЗадания.ПолучитьРезультатФоновогоЗадания(ИдентификаторЗадания, , Истина). Третий параметр Истина означает, что мы не будем ждать завершения задания. Если задание еще не выполнено, эта функция просто вернет Неопределено, что позволяет нам проверять статус без блокировки.

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

Дополнительные рекомендации и советы

  1. Отображение прогресса: Для улучшения пользовательского опыта мы можем использовать общую форму ДлительнаяОперация или ее аналоги, чтобы показывать пользователю индикатор выполнения, пока фоновое задание работает. В обработчике ожидания можно обновлять прогресс, если фоновое задание предоставляет такую информацию. Например, можно вывести индикатор прогресса в ячейке динамического списка или использовать готовый подход для индикации прогресса выполнения фонового задания на управляемой форме внешней обработки.
  2. Обработка ошибок: Всегда предусматривайте обработку ошибок в фоновых заданиях. Ошибки должны быть логированы, а их описание должно быть доступно клиенту через статус фонового задания (СостояниеЗадания.ОписаниеОшибки). Для автоматического контроля за сбоями подойдёт мониторинг ошибок фоновых заданий в Telegram.
  3. Удаление завершенных заданий: Завершенные фоновые задания занимают место в таблице РегламентныеЗадания. Рекомендуется периодически очищать эту таблицу от старых и ненужных заданий. Это можно делать вручную или настроить регламентное задание для автоматической очистки — для этого подойдёт инструментарий управления и отладки фоновых заданий.
  4. Ограничение числа одновременных заданий: Если вы планируете запускать много фоновых заданий, рассмотрите возможность ограничения их числа, чтобы не перегружать сервер. Это можно реализовать с помощью дополнительных механизмов контроля в вашей конфигурации.
  5. Параметры и результаты: Убедитесь, что все параметры, передаваемые в фоновое задание, и результат, возвращаемый им, могут быть сериализованы. Обычно это касается примитивных типов, структур, массивов, ссылочных типов и менеджеров объектов.

Заключение

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

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

← На главную