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

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

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

Выясняем причину блокировки интерфейса

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

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

Как только мы вызываем серверный метод, основной поток клиентского приложения приостанавливается. Если внутри этого метода мы начинаем «ждать» результат фонового задания, то смысл использования фонового процесса теряется — пользователь все равно видит замерший экран.

Правильный алгоритм запуска фоновой операции

Разберем по шагам корректную схему взаимодействия клиента и сервера, которая позволит форме оставаться «живой»:

  1. На клиенте: Подключаем обработчик ожидания, который будет периодически проверять статус задания.
  2. На клиенте: Вызываем серверную функцию запуска.
  3. На сервере: Создаем фоновое задание с помощью ФоновыеЗадания.Выполнить() и немедленно возвращаем его уникальный идентификатор (UUID) на клиент, не дожидаясь завершения.
  4. На клиенте: В процедуре обработчика ожидания периодически вызываем (не чаще чем раз в 2-3 секунды) легкую серверную функцию для проверки состояния задания по его UUID.
  5. На сервере: Проверяем свойство Состояние объекта фонового задания.
  6. На клиенте: Если состояние «Завершено» — отключаем обработчик ожидания и получаем результат.

Пример программной реализации

Рассмотрим пример кода, который реализует данную схему. Сначала подготовим процедуру запуска на клиенте:


&НаКлиенте
Процедура ЗапуститьРасчет(Команда)
    
    // Очищаем предыдущие результаты, если нужно
    Объект.РезультатРасчета = "";
    
    // 1. Подключаем проверку статуса (раз в 2 секунды)
    ПодключитьОбработчикОжидания("Обработчик_ПроверитьСтатусЗадания", 2);
    
    // 2. Запускаем задание на сервере и получаем ИД
    ДанныеЗадания = ЗапуститьЗаданиеНаСервере();
    ИдентификаторЗадания = ДанныеЗадания.Идентификатор;
    АдресХранилища = ДанныеЗадания.АдресРезультата;
    
КонецПроцедуры

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


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

Мониторинг состояния без «зависаний»

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


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

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

Важные технические нюансы

Разберем несколько критических моментов, которые часто упускают при работе с фоновыми заданиями:

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

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

3. Временное хранилище. При создании адреса во временном хранилище на сервере (через ПоместитьВоВременноеХранилище) обязательно вторым параметром передавайте ЭтаФорма.УникальныйИдентификатор. Это гарантирует, что данные не «протухнут» до того момента, как фоновое задание их туда запишет, а форма — считает.

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

Использование БСП (Библиотеки стандартных подсистем)

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

Таким образом, использование правильной архитектуры «Клиент -> Запуск -> Мгновенный возврат -> Проверка по таймеру» позволяет сделать интерфейс отзывчивым, избавиться от «зависаний» и обеспечить комфортную работу пользователя даже при выполнении самых сложных и длительных расчетов.

← На главную