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