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

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

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

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

Почему классическая схема автора темы не работает напрямую?

Автор темы изначально хотел использовать следующую схему: запустить выполнение кода, на время приостановить его, передать управление на клиент для обновления формы, а затем продолжить выполнение серверного кода дальше. Физически в архитектуре 1С «клиент-сервер» это сделать невозможно. Пока выполняется контекстный или неконтекстный вызов сервера, клиентский поток ожидает ответа и заблокирован. Мы не можем «проснуться» на клиенте посреди выполнения одной серверной транзакции или процедуры, чтобы перерисовать форму.

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

Решение 1. Реализация с помощью Библиотеки Стандартных Подсистем (БСП)

Если ваша конфигурация построена на БСП (а это практически все типовые решения от 1С: БП, УТ, ERP, ЗУП, УНФ), то изобретать велосипед не нужно. В БСП есть готовая подсистема ДлительныеОперации, которая берет на себя всю рутину по управлению фоновыми заданиями, отслеживанию их состояния и передаче прогресса на клиент.

Рассмотрим пошагово, как реализовать пошаговое выполнение серверного кода с обновлением прогресса на форме.

Шаг 1. Создание серверного метода в общем модуле

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


Процедура ВыполнитьТяжелуюОперацию(Параметры, АдресРезультата) Экспорт
    
    // Шаг 1. Подготовка данных
    ДанныеДляОбработки = ПодготовитьДанные(Параметры);
    
    // Передаем на клиент информацию о завершении первого шага (процент выполнения и текст)
    ДлительныеОперации.СообщитьПрогресс(25, "Шаг 1 из 4: Данные успешно подготовлены...");
    
    // Шаг 2. Основная обработка
    ОбработатьДанные(ДанныеДляОбработки);
    ДлительныеОперации.СообщитьПрогресс(60, "Шаг 2 из 4: Завершена обработка данных...");
    
    // Шаг 3. Запись результатов
    ЗаписатьРезультатыВБазу(ДанныеДляОбработки);
    ДлительныеОперации.СообщитьПрогресс(90, "Шаг 3 из 4: Данные сохранены в базу данных...");
    
    // Шаг 4. Формирование финального отчета
    ФинальныйОтчет = СформироватьФинальныйОтчет();
    
    // Помещаем результат работы во временное хранилище, чтобы клиент мог его забрать
    ПоместитьВоВременноеХранилище(ФинальныйОтчет, АдресРезультата);
    
    ДлительныеОперации.СообщитьПрогресс(100, "Шаг 4 из 4: Операция успешно завершена!");
    
КонецПроцедуры

Шаг 2. Подготовка управляемой формы

На форме нашей обработки или элемента справочника нам понадобятся два реквизита:

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

Шаг 3. Запуск фонового процесса с клиента

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


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

&НаСервере
Функция ЗапуститьВыполнениеНаСервере()
    
    // Создаем структуру параметров для нашей процедуры
    ПараметрыПроцедуры = Новый Структура;
    ПараметрыПроцедуры.Вставить("ИмяПользователя", Пользователи.ТекущийПользователь());
    
    // Подготавливаем параметры запуска фонового задания
    ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
    ПараметрыВыполнения.НаименованиеФоновогоЗадания = "Демо: Пошаговая длительная операция";
    
    // Запускаем процедуру общего модуля в фоне
    Возврат ДлительныеОперации.ВыполнитьВФоне(
        "МойОбщийМодульСервер.ВыполнитьТяжелуюОперацию", 
        ПараметрыПроцедуры, 
        ПараметрыВыполнения
    );
    
КонецФункции

Шаг 4. Обработка прогресса и завершения на клиенте

Нам осталось написать две клиентские процедуры ожидания, которые мы зарегистрировали на предыдущем шаге в объектах ОписаниеОповещения.


&НаКлиенте
Процедура ОбновитьПрогрессНаФорме(ДанныеПрогресса, ДополнительныеПараметры) Экспорт
    
    Если ДанныеПрогресса = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Считываем структуру прогресса, которую передал метод СообщитьПрогресс
    Попытка
        Процент = ДанныеПрогресса.Прогресс.Процент;
        ТекстСтатуса = ДанныеПрогресса.Прогресс.Текст;
        
        // Обновляем реквизиты на форме
        Объект.ИндикаторПрогресса = Процент;
        Элементы.СтатусВыполнения.Заголовок = ТекстСтатуса;
    Исключение
        // Обработка исключений при чтении данных
    КонецПопытки;
    
КонецПроцедуры

&НаКлиенте
Процедура ЗавершитьДлительнуюОперацию(Результат, ДополнительныеПараметры) Экспорт
    
    Если Результат = Неопределено Тогда
        // Операция была отменена пользователем
        Элементы.СтатусВыполнения.Заголовок = "Операция отменена.";
        Возврат;
    КонецЕсли;
    
    Если Результат.Статус = "Выполнено" Тогда
        // Считываем результат из Временного Хранилища
        ОбработатьРезультат(Результат.АдресРезультата);
        Объект.ИндикаторПрогресса = 100;
        Элементы.СтатусВыполнения.Заголовок = "Все задачи успешно выполнены!";
        ПоказатьОповещениеПользователя("Готово", , "Процесс успешно завершен!");
    ИначеЕсли Результат.Статус = "Ошибка" Тогда
        // Если в фоновом задании произошла непредвиденная ошибка (аварийное завершение)
        Объект.ИндикаторПрогресса = 0;
        Элементы.СтатусВыполнения.Заголовок = "Произошла ошибка при выполнении!";
        ВызватьИсключение Результат.ПодробноеПредставлениеОшибки;
    КонецЕсли;
    
КонецПроцедуры

Использование БСП — это максимально надежный, быстрый в разработке и отказоустойчивый путь. Механизм БСП автоматически решает проблемы с зависанием сессий, очисткой временного хранилища и корректным логированием ошибок в журнал регистрации.

Решение 2. Реализация без БСП (на чистой платформе 1С)

Бывают ситуации, когда вы работаете в самописной конфигурации, где БСП отсутствует, или пишете внешнюю обработку, которая должна быть абсолютно автономной и работать на любой базе. В этом случае мы можем реализовать аналогичный механизм «руками», используя стандартные объекты платформы ФоновыеЗадания и ПодключитьОбработчикОжидания.

Для передачи прогресса с сервера на клиент без БСП мы применим архитектурный шаблон с использованием Временного хранилища. Фоновое задание имеет доступ к временному хранилищу сеанса-родителя. Мы можем периодически записывать туда структуру состояния, а клиент будет ее считывать.

Серверная часть (в общем модуле или модуле объекта):


Процедура ВыполнитьЗадачуБезБСП(Параметры, АдресПрогресса) Экспорт
    
    // Будем выполнять операцию в цикле
    ВсегоШагов = 5;
    Для ТекущийШаг = 1 По ВсегоШагов Цикл
        
        // Имитируем тяжелую работу на 2 секунды
        ФизическаяПауза(2); 
        
        // Рассчитываем процент
        Процент = (ТекущийШаг / ВсегоШагов) * 100;
        ТекстСтатуса = "Выполняется шаг " + ТекущийШаг + " из " + ВсегоШагов + "...";
        
        // Записываем структуру прогресса во Временное хранилище по адресу, переданному клиентом
        ДанныеПрогресса = Новый Структура("Процент, Текст, Завершено", Процент, ТекстСтатуса, Ложь);
        ПоместитьВоВременноеХранилище(ДанныеПрогресса, АдресПрогресса);
        
    КонецЦикла;
    
    // Записываем финальный статус завершения
    ДанныеПрогресса = Новый Структура("Процент, Текст, Завершено", 100, "Выполнение завершено успешно!", Истина);
    ПоместитьВоВременноеХранилище(ДанныеПрогресса, АдресПрогресса);
    
КонецПроцедуры

Процедура ФизическаяПауза(Секунды)
    // Простой способ сделать паузу на сервере без загрузки процессора
    ТекущаяДата = ТекущаяДата();
    Пока ТекущаяДата() < ТекущаяДата + Секунды Цикл
        // Ожидание
    КонецЦикла;
КонецПроцедуры

Клиентская часть управляемой формы:

На форме также разместим реквизиты ИндикаторПрогресса и СтатусВыполнения. Также добавим реквизиты формы ИдентификаторЗадания (тип: УникальныйИдентификатор) и АдресХранилищаПрогресса (тип: Строка) для хранения служебной информации между вызовами обработчика ожидания.


&НаКлиенте
Процедура НачатьРаботу(Команда)
    
    // Инициализируем реквизиты на форме
    Объект.ИндикаторПрогресса = 0;
    Элементы.СтатусВыполнения.Заголовок = "Инициализация...";
    
    // Подготавливаем уникальный адрес во Временном Хранилище
    АдресХранилищаПрогресса = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
    
    // Запускаем фоновое задание напрямую через менеджер фоновых заданий
    ИдентификаторЗадания = ЗапуститьЗаданиеНаСервере(АдресХранилищаПрогресса);
    
    // Подключаем стандартный обработчик ожидания платформы.
    // Метод ПроверитьСостояниеЗадания будет вызываться каждую секунду.
    ПодключитьОбработчикОжидания("ПроверитьСостояниеЗадания", 1, Ложь);
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция ЗапуститьЗаданиеНаСервере(АдресХранилищаПрогресса)
    
    ПараметрыЗапуска = Новый Массив;
    ПараметрыЗапуска.Добавить(Новый Структура); // Входящие параметры алгоритма
    ПараметрыЗапуска.Добавить(АдресХранилищаПрогресса);
    
    // Запуск фонового задания платформы
    Задание = ФоновыеЗадания.Выполнить(
        "МойОбщийМодульСервер.ВыполнитьЗадачуБезБСП", 
        ПараметрыЗапуска, 
        "МояУникальнаяОперация"
    );
    
    Возврат Задание.УникальныйИдентификатор;
    
КонецФункции

Код обработчика ожидания на клиенте:


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

&НаСервереБезКонтекста
Функция ПолучитьИзХранилищаНаСервере(Адрес)
    Возврат ПолучитьИзВременногоХранилища(Адрес);
КонецФункции

&НаСервереБезКонтекста
Функция ПроверитьАктивностьЗадания(Идентификатор)
    
    Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
    Если Задание = Неопределено Тогда
        Возврат Ложь;
    КонецЕсли;
    
    Возврат (Задание.Состояние = СостояниеФоновогоЗадания.Активно);
    
КонецФункции

Решение 3. Пошаговое выполнение на клиенте через Обработчик ожидания (без фоновых заданий)

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

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

Рассмотрим пример реализации этой схемы на форме:


&НаКлиенте
Перем ТекущийШагЭкспедиции; // Используем глобальную переменную модуля формы

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

&НаКлиенте
Процедура ВыполнитьОчереднойШаг() Экспорт
    
    Если ТекущийШагЭкспедиции = 1 Тогда
        
        // Выполняем первую часть логики
        ВыполнитьПервыйЭтапНаСервере();
        Объект.ИндикаторПрогресса = 33;
        Элементы.СтатусВыполнения.Заголовок = "Завершен этап 1 из 3...";
        
        ТекущийШагЭкспедиции = 2;
        // Подключаем обработчик заново для следующего шага
        ПодключитьОбработчикОжидания("ВыполнитьОчереднойШаг", 0.1, Истина);
        
    ИначеЕсли ТекущийШагЭкспедиции = 2 Тогда
        
        ВыполнитьВторойЭтапНаСервере();
        Объект.ИндикаторПрогресса = 66;
        Элементы.СтатусВыполнения.Заголовок = "Завершен этап 2 из 3...";
        
        ТекущийШагЭкспедиции = 3;
        ПодключитьОбработчикОжидания("ВыполнитьОчереднойШаг", 0.1, Истина);
        
    ИначеЕсли ТекущийШагЭкспедиции = 3 Тогда
        
        ВыполнитьТретийЭтапНаСервере();
        Объект.ИндикаторПрогресса = 100;
        Элементы.СтатусВыполнения.Заголовок = "Все этапы успешно выполнены!";
        
        ПоказатьОповещениеПользователя("Успешно", , "Пошаговая обработка завершена.");
        
    КонецЕсли;
    
КонецПроцедуры

Преимущества пошагового клиентского метода:

Недостатки метода:

Основные правила и рекомендации при работе с длительными операциями

Чтобы ваши длительные операции работали стабильно и не вызывали сбоев в информационной базе, придерживайтесь следующих архитектурных правил:

  1. Не злоупотребляйте частотой обновления прогресса. Если у вас цикл на 10 000 итераций, вызывать СообщитьПрогресс на каждом шаге — грубейшая ошибка. Это приведет к колоссальной нагрузке на базу данных и память сервера. Обновляйте прогресс не чаще одного раза в 1-2 секунды или порциями (например, каждые 5% или 10% прогресса).
  2. Следите за транзакциями. Метод СообщитьПрогресс (и запись во временное хранилище) не передаст данные на клиент, пока выполняется активная транзакция на сервере. Если весь ваш тяжелый код обернут в одну конструкцию НачатьТранзакцию() ... ЗафиксироватьТранзакцию(), то клиент увидит только 0% в начале и 100% в самом конце, так как данные внутри транзакции изолированы. Старайтесь фиксировать транзакции порционно внутри фонового задания.
  3. Обрабатывайте ручную отмену. Предоставьте пользователю кнопку «Отменить». При ее нажатии на клиенте вызывайте метод ФоновыеЗадания.Отменить(ИдентификаторЗадания) или используйте механизмы БСП для прерывания работы. На сервере в цикле проверяйте состояние выполнения, чтобы фоновое задание вовремя прекращало работу, если пользователь передумал ждать.

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

← На главную