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