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

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

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

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

Основа решения: Фоновые задания

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

Процесс организации выполнения в фоновом задании включает следующие шаги:

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

Рассмотрим общий принцип запуска фонового задания:


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

    // Устанавливаем параметры, если необходимо
    Для Каждого КлючЗначение Из Параметры Цикл
        ОбработкаОбъект[КлючЗначение.Ключ] = КлючЗначение.Значение;
    КонецЦикла;

    // Вызываем метод длительной операции в контексте обработки
    ОбработкаОбъект.ДлительнаяОперацияСПрогрессом();
КонецПроцедуры

// В модуле объекта внешней обработки, экспортная процедура
Процедура ДлительнаяОперацияСПрогрессом() Экспорт
    ОбщееКоличествоШагов = 1000;
    Для Шаг = 1 По ОбщееКоличествоШагов Цикл
        // Выполняем часть работы
        // ...

        // Сообщаем о прогрессе
        ПроцентВыполнения = Цел(Шаг / ОбщееКоличествоШагов * 100);
        СообщитьПрогресс(ПроцентВыполнения, "Выполняется шаг " + Шаг + " из " + ОбщееКоличествоШагов);

        // Даем системе возможность обновить состояние фонового задания
        Если Шаг % 10 = 0 Тогда // Например, обновляем каждый 10-й шаг
            ДлительныеОперацииКлиентСервер.СообщитьПрогресс(ПроцентВыполнения); // Использование БСП, если она есть
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

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

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

    // 3. Запускаем фоновое задание
    ФоновоеЗадание = ФоновыеЗадания.Выполнить("ОбщийМодульПрослойка.ВыполнитьДлительнуюОперацию", ПараметрыФоновогоЗадания, , "Длительная операция");

    // 4. Запускаем обработчик ожидания для проверки статуса
    ПодключитьОбработчикОжидания("ПроверитьСтатусФоновогоЗадания", 1, Истина);
    ЗапомнитьИдентификаторФоновогоЗадания(ФоновоеЗадание.УникальныйИдентификатор);
КонецПроцедуры

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

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

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

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

Реализация без изменения конфигурации: Использование прослойки

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

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

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

Давайте подробнее рассмотрим каждый шаг:

1. Подготовка внешней обработки

Ваша внешняя обработка должна иметь экспортную процедуру, которая будет выполнять длительную операцию. Например:


// Модуль объекта внешней обработки
Процедура ВыполнитьДлительнуюОперацию(Параметры) Экспорт
    // Здесь мы будем выполнять основную работу.
    // Параметры могут содержать данные, переданные с клиента.
    ОбщееКоличество = 100;
    Для Итерация = 1 По ОбщееКоличество Цикл
        // ... Ваш код длительной операции ...

        // Сообщаем прогресс
        Процент = Цел(Итерация / ОбщееКоличество * 100);
        СообщениеПрогресса = "Обработано " + Итерация + " из " + ОбщееКоличество;
        
        // Для демонстрации, как это можно сделать без БСП,
        // сохраним прогресс в таблицу временного хранения или регистр сведений
        // А затем на клиенте будем опрашивать эти данные по ИД фонового задания.
        // Более правильный подход с Состояние() или БСП будет рассмотрен далее.
        ЗаписьЛогаПрогресса(ИдентификаторФоновогоЗадания(), Процент, СообщениеПрогресса);

        // Чтобы не перегружать сервер частыми обновлениями
        Если Итерация % 5 = 0 Тогда
            ВнешниеОбработки.УстановитьПрогресс(Процент, СообщениеПрогресса); // Если обработка запущена как доп.обработка через механизм платформы
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

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

2. Общий модуль-прослойка (Сервер)

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


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

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

        // Передаем параметры в обработку. Это может быть структура, массив и т.д.
        ОбработкаОбъект.Параметры = ПараметрыДляОбработки;

        // Вызываем экспортную процедуру внешней обработки
        ОбработкаОбъект.ВыполнитьДлительнуюОперацию(ПараметрыДляОбработки);
        
        // Завершение с сообщением
        ФоновыеЗадания.ТекущееФоновоеЗадание().УстановитьСостояние("Выполнено успешно.");
        
    Исключение Как Искл
        // Логируем ошибку и устанавливаем состояние фонового задания как "Ошибка"
        ЗаписьЖурналаРегистрации(
            "ВнешниеОбработки.ДлительныеОперации",
            УровеньВажности.Ошибка,
            ,,
            "Ошибка при выполнении длительной операции внешней обработки: " + Искл.Описание
        );
        ФоновыеЗадания.ТекущееФоновоеЗадание().УстановитьСостояние("Ошибка: " + Искл.Описание);
        
        // Перевыбрасываем исключение, чтобы оно было зафиксировано в журнале фоновых заданий
        ВызватьИсключение Искл.Описание;
    КонецПопытки;
    
КонецПроцедуры

// Процедура для чтения прогресса с клиента
Процедура ПолучитьПрогрессФоновогоЗадания(ИдентификаторФоновогоЗадания) Экспорт
    // Здесь должен быть код, который читает сохраненный прогресс
    // Например, из регистра сведений, где его сохраняла длительная операция.
    // Если используется БСП, то это ДлительныеОперацииКлиентСервер.ПрочитатьПрогресс.
    // В простейшем случае, можно использовать ФоновыеЗадания.Состояние()
    // и ФоновыеЗадания.ПолучитьСообщения() для получения текстовых сообщений.
    
    Состояние = ФоновыеЗадания.Состояние(ИдентификаторФоновогоЗадания);
    Процент = 0;
    Описание = "";
    
    Если Состояние = СостояниеФоновогоЗадания.Выполняется Тогда
        // Пример получения прогресса из сообщений фонового задания
        // (если внутри операции вызывался ФоновыеЗадания.ТекущееФоновоеЗадание().СообщитьПрогресс(Процент, ОписаниеПрогресса);)
        Для Каждого Сообщение Из ФоновыеЗадания.ПолучитьСообщения(ИдентификаторФоновогоЗадания) Цикл
            Если Сообщение.ТипСообщения = ТипСообщенияФоновогоЗадания.Прогресс Тогда
                Процент = Сообщение.Прогресс;
                Описание = Сообщение.Описание;
            КонецЕсли;
        КонецЦикла;
    ИначеЕсли Состояние = СостояниеФоновогоЗадания.Выполнено Тогда
        Процент = 100;
        Описание = "Завершено.";
    ИначеЕсли Состояние = СостояниеФоновогоЗадания.Ошибка Тогда
        Процент = 100; // Прогресс не актуален, но показываем, что дошли до конца
        Описание = "Ошибка: " + ФоновыеЗадания.ПолучитьОшибку(ИдентификаторФоновогоЗадания);
    КонецЕсли;
    
    Возврат Новый Структура("Состояние, Процент, Описание", Состояние, Процент, Описание);
КонецПроцедуры

3. Клиентская часть внешней обработки

На клиентской стороне внешней обработки мы инициируем весь процесс:

  1. Читаем двоичные данные самой внешней обработки.
  2. Помещаем их во временное хранилище на сервере, получая адрес.
  3. Передаем этот адрес и необходимые параметры в процедуру общего модуля-прослойки, запуская ее как фоновое задание.
  4. Запускаем ОбработчикОжидания для периодического опроса статуса фонового задания.
  5. Обновляем индикатор прогресса на форме.

// Модуль формы внешней обработки (Клиент)

Перем мИдентификаторФоновогоЗадания Экспорт; // Храним ИД фонового задания

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

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

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

    // 5. Подключаем обработчик ожидания для проверки статуса
    ПодключитьОбработчикОжидания("ПроверитьПрогрессНаСервере", 1, Истина); // Проверять каждую секунду
КонецПроцедуры

&НаКлиенте
Процедура ПроверитьПрогрессНаСервере()
    Если мИдентификаторФоновогоЗадания = Неопределено Тогда
        ОтключитьОбработчикОжидания("ПроверитьПрогрессНаСервере");
        Возврат;
    КонецЕсли;

    Результат = ПолучитьПрогрессССервера(мИдентификаторФоновогоЗадания);

    Элементы.ПрогрессПоле.Значение = Результат.Процент;
    Элементы.СостояниеТекст.Заголовок = Результат.Описание;

    Если Результат.Состояние = СостояниеФоновогоЗадания.Выполнено Тогда
        Элементы.СостояниеТекст.Заголовок = "Операция успешно завершена!";
        ОтключитьОбработчикОжидания("ПроверитьПрогрессНаСервере");
        мИдентификаторФоновогоЗадания = Неопределено;
    ИначеЕсли Результат.Состояние = СостояниеФоновогоЗадания.Ошибка Тогда
        Элементы.СостояниеТекст.Заголовок = "Операция завершена с ошибкой: " + Результат.Описание;
        ОтключитьОбработчикОжидания("ПроверитьПрогрессНаСервере");
        мИдентификаторФоновогоЗадания = Неопределено;
    КонецЕсли;
КонецПроцедуры

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

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

&НаСервереБезКонтекста
Функция ПолучитьПрогрессССервера(ИдентификаторФоновогоЗадания)
    Возврат ОбщийМодульПрослойка.ПолучитьПрогрессФоновогоЗадания(ИдентификаторФоновогоЗадания);
КонецФункции

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

Использование функционала БСП "Длительные операции"

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

Основные компоненты БСП для длительных операций:

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

Процесс использования БСП выглядит следующим образом:

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

// Модуль объекта внешней обработки
Процедура МояДлительнаяОперация(Параметры) Экспорт
    ОбщееКоличество = 1000;
    Для И = 1 По ОбщееКоличество Цикл
        // ... Выполняем полезную работу ...
        
        // Сообщаем прогресс
        Процент = Цел(И / ОбщееКоличество * 100);
        ТекстСообщения = "Выполняется: " + Процент + "% (" + И + " из " + ОбщееКоличество + ")";
        ДлительныеОперации.СообщитьПрогресс(Процент, ТекстСообщения);
        
        // Дополнительная задержка для демонстрации
        Если И % 50 = 0 Тогда
            НачатьТранзакцию();
            ЗафиксироватьТранзакцию();
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

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

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

Использование механизма уведомлений (с версии платформы 8.3.26)

С версии платформы 8.3.26 появился более современный и эффективный способ взаимодействия между сервером и клиентом – механизм уведомлений. Этот механизм позволяет серверу инициативно отправлять сообщения клиенту, не дожидаясь клиентского запроса. Это устраняет необходимость в постоянном "опросе" сервера со стороны клиента (техника polling), что снижает нагрузку на сеть и сервер.

Принцип работы:

  1. На сервере, в длительной процедуре, используем метод МенеджерУведомленийКлиента.ОтправитьУведомление или МенеджерУведомленийКлиента.ОтправитьУведомлениеМенеджеруКлиента, передавая текущий процент выполнения и статус.
  2. На клиенте, в форме внешней обработки, подключаем обработчик уведомлений клиента.
  3. Когда клиент получает уведомление, он обновляет индикатор прогресса на форме.

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

// Модуль формы внешней обработки (Клиент)
Перем мИдентификаторКлиента Экспорт; // Идентификатор текущей клиентской сессии

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

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

&НаСервереБезКонтекста
Функция ПолучитьИдентификаторТекущегоКлиентаНаСервере()
    Возврат МенеджерУведомленийКлиента.ИдентификаторТекущегоКлиента();
КонецФункции

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

Механизм уведомлений является наиболее современным и эффективным способом обратной связи от сервера к клиенту, особенно для длительных операций, так как он реактивен и не требует постоянного опроса.

Отображение прогресса непосредственно на форме

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

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

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


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

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

Дополнительные аспекты и советы

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

← На главную