Как в 1С отображать прогресс выполнения длительной операции на форме в реальном времени?

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

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

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


&НаКлиенте
Процедура ПереподставитьДоговорСчетНаОплату(Команда)
    выборкаДокументы = ЗапросСчетов(); // Предположим, что эта функция возвращает массив или ТЗ
    для каждого впДокумент из выборкаДокументы цикл
        ПереподставитьДоговорСчетНаОплатуНаСервере(впДокумент);
        Сообщить(впДокумент) ;
    конецЦикла;
КонецПроцедуры

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

Решение 1: Использование обработчика ожидания

Этот метод является самым прямым решением проблемы без использования сложных фоновых заданий. Идея состоит в том, чтобы разбить один большой монолитный цикл на множество маленьких, независимых вызовов. Каждый вызов будет обрабатывать один элемент, выводить сообщение, а затем планировать следующий вызов через короткий промежуток времени. Это дает интерфейсу «передышку» для обновления.

Разберем по шагам:

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

Посмотрим на пример. Добавим на форму реквизит ДокументыДляОбработки (тип Массив).


&НаКлиенте
Процедура ВыполнитьОбработку(Команда)
    // Шаг 1: Получаем все данные и сохраняем в реквизит формы
    ДокументыДляОбработки = ЗапросСчетовНаСервере();
    
    // Проверяем, есть ли что обрабатывать
    Если ДокументыДляОбработки.Количество() > 0 Тогда
        // Шаг 2: Запускаем обработчик в первый раз
        ПодключитьОбработчикОжидания("ОбработатьОдинДокумент", 0.1, Истина);
    Иначе
        Сообщить("Нет документов для обработки!");
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура ОбработатьОдинДокумент()
    // Шаг 3: Обрабатываем один элемент
    Если ДокументыДляОбработки.Количество() = 0 Тогда
        // Список пуст, завершаем работу
        ОтключитьОбработчикОжидания("ОбработатьОдинДокумент");
        Сообщить("Обработка успешно завершена!");
        Возврат;
    КонецЕсли;
    
    // Берем первый документ из массива
    ТекущийДокумент = ДокументыДляОбработки[0];
    
    // Выполняем серверный вызов
    ПереподставитьДоговорСчетНаОплатуНаСервере(ТекущийДокумент);
    
    // Выводим сообщение в реальном времени
    Сообщить("Обработан: " + ТекущийДокумент);
    
    // Удаляем обработанный документ из массива
    ДокументыДляОбработки.Удалить(0);
    
    // Шаг 4: Планируем следующий вызов (если остались документы)
    Если ДокументыДляОбработки.Количество() > 0 Тогда
        ПодключитьОбработчикОжидания("ОбработатьОдинДокумент", 0.1, Истина);
    Иначе
        // Это был последний документ, отключаемся
        ОтключитьОбработчикОжидания("ОбработатьОдинДокумент");
        Сообщить("Обработка успешно завершена!");
    КонецЕсли;
    
КонецПроцедуры

Плюсы метода:

Минусы метода:

Решение 2: Длительные операции и фоновые задания

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

Стандартные конфигурации часто содержат подсистему для работы с длительными операциями (например, общий модуль ДлительныеОперации). Общая схема работы выглядит так:

  1. Инициация. Клиент вызывает серверную функцию, которая подготавливает данные, помещает их во временное хранилище и запускает фоновое задание для их обработки. Эта функция возвращает клиенту адрес хранилища с параметрами задачи.
  2. Отображение статуса. Клиент открывает специальную форму ожидания (или использует индикатор на текущей форме) и с помощью обработчика ожидания периодически (например, раз в 2-3 секунды) обращается к серверу, чтобы узнать статус выполнения фонового задания.
  3. Обновление прогресса. Фоновое задание в процессе своей работы периодически обновляет информацию о своем состоянии (например, «Обработано 50 из 100 документов») в структуре, хранящейся во временном хранилище.
  4. Завершение. Когда клиент узнает, что задание завершено, он отключает обработчик ожидания, закрывает форму прогресса и сообщает пользователю результат.

Плюсы метода:

Минусы метода:

Важные замечания по производительности и удобству

Вне зависимости от выбранного способа, стоит помнить о некоторых нюансах.

1. Сообщить() — это медленно.

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

2. Используйте более подходящие элементы интерфейса.

Бесконечная «простыня» из сообщений в служебном окне неудобна для пользователя. Рассмотрите альтернативы:

Эти элементы обновляются так же, как и сообщения, и отлично работают в связке с обработчиком ожидания.

В итоге, выбор метода зависит от задачи. Для относительно быстрых операций (до минуты) и когда некритично, что пользователь не сможет работать в программе, отлично подойдет обработчик ожидания. Для тяжелых, долгих процессов, где важна отзывчивость интерфейса, единственно верным решением будет использование фоновых заданий.

← На главную