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

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

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

Почему возникает ошибка «Поле объекта не обнаружено (Значение)»

Выясним причину этой ошибки. Элемент управляемой формы с видом «Поле индикатора» устроен иначе, чем аналогичный элемент в обычном приложении. У элемента Элементы.Индикатор нет собственного свойства Значение. У него доступны только визуальные границы шкалы: МинимальноеЗначение и МаксимальноеЗначение.

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

Посмотрим на пример правильного изменения реквизита, если бы процедура выполнялась на клиенте:


// ПрогрессОбработки — числовой реквизит формы, указанный в свойстве "ПутьКДанным" элемента
ПрогрессОбработки = ПрогрессОбработки + 1;

Ограничение клиент-серверной архитектуры

Проанализируем ключевое архитектурное ограничение платформы 1С:Предприятие 8.3: при синхронном серверном вызове клиентский интерфейс полностью блокируется. Пока управление не вернется с сервера на клиент, платформа не перерисовывает форму и не обрабатывает события интерфейса.

Если запустить цикл на сервере и даже правильно менять значение реквизита формы, на экране пользователя ничего происходить не будет. Клиент увидит изменение только в тот момент, когда процедура полностью завершится и управление вернется на клиента. Более того, метод Состояние() доступен исключительно на клиенте и не может быть вызван в контексте &НаСервере.

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

Вариант 1. Использование механизма фоновых заданий и подсистемы «Длительные операции» (БСП)

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

  1. Серверная процедура запускается как фоновое задание через функционал БСП.
  2. Внутри фонового задания в цикле вызывается метод передачи прогресса:

// Внутри серверной процедуры фонового задания:
Для НомерСтроки = 1 По ВсегоСтрок Цикл
    // Выполнение полезной работы
    
    // Передаем процент выполнения и текст
    Процент = Окр((НомерСтроки / ВсегоСтрок) * 100);
    ДлительныеОперации.СообщитьПрогресс(Процент, "Обработка данных...");
КонецЦикла;

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

Вариант 2. Фоновое задание без БСП (через временное хранилище или сообщения)

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

  1. На клиенте формируем уникальный идентификатор и адрес во временном хранилище:
    
    АдресХранилища = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
    
  2. Запускаем фоновое задание через менеджер фоновых заданий, передав туда сформированный АдресХранилища.
  3. Внутри фонового задания с определенным шагом (например, раз в 1–5% или через каждые N итераций) записываем прогресс: ПоместитьВоВременноеХранилище(ТекущийПрогресс, АдресХранилища); Не следует выполнять запись на каждой итерации, чтобы не создавать чрезмерную нагрузку на сервер операциями ввода-вывода.
  4. На клиенте подключаем периодический опрос через обработчик ожидания:
    
    &НаКлиенте
    Процедура ЗапуститьОбработку()
        // Запуск фонового задания...
        ПодключитьОбработчикОжидания("ПроверитьПрогресс", 1);
    КонецПроцедуры
    
    &НаКлиенте
    Процедура ПроверитьПрогресс()
        ДанныеПрогресса = ПолучитьИзВременногоХранилища(АдресХранилища);
        Если ДанныеПрогресса <> Неопределено Тогда
            ПрогрессФормы = ДанныеПрогресса;
        КонецЕсли;
        
        // Проверяем завершение фонового задания
        Если ЗаданиеЗавершено() Тогда
            ОтключитьОбработчикОжидания("ПроверитьПрогресс");
        КонецЕсли;
    КонецПроцедуры
    

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

Вариант 3. Порционная обработка данных на клиенте

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

Схема работы выглядит следующим образом:

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

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

← На главную