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