Как реализовать прогресс-бар во встроенной обработке 1С 8.3 с использованием БСП и передать ТаблицуЗначений на сервер?

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

Мы рассмотрим, как создать прогресс-бар для длительной операции (5-20 минут) во встроенной обработке 1С, не блокируя пользовательский интерфейс. Особое внимание уделим одной из самых частых проблем — корректной передаче больших объемов данных, таких как таблица значений, из клиентской формы на сервер для обработки в фоновом режиме. Наши решения будут основываться на возможностях платформы 8.3.18.1334, конфигурации Бухгалтерия Предприятия 3.0.95.24 и Библиотеки Стандартных Подсистем (БСП) 3.1.3.494.

Почему подсистема "Длительные операции" из БСП — оптимальное решение?

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

Она идеально подходит для нашей задачи, поскольку позволяет:

  1. Выполнять код на сервере без блокировки клиента.
  2. Отображать прогресс выполнения операции в специальном окне.
  3. Гибко управлять параметрами выполнения и ожидания.

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

Ключевая сложность: Передача ТаблицыЗначений на сервер

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

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

Реализация прогресс-бара с передачей ТаблицыЗначений: Пошаговая инструкция

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

Шаг 1: Подготовка на форме обработки (Клиентский модуль)

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

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

Пример кода клиентского модуля формы:


&НаКлиенте
Процедура НачатьВыполнение(Команда)
    ЭтаФорма.РезультатВыполнения = "";

    // 1. Подготовка параметров для фонового задания
    ПараметрыЗапуска = Новый Структура;
    ПараметрыЗапуска.Вставить("ДатаНачала1", НачалоДня(ЭтаФорма.ДатаНачала));
    ПараметрыЗапуска.Вставить("ДатаОкончания1", КонецДня(ЭтаФорма.ДатаОкончания));

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

    // 3. Запускаем фоновое задание на сервере
    СтруктураФоновогоЗадания = ВыполнитьФоновоеЗаданиеНаСервере(ПараметрыЗапуска, УникальныйИдентификатор);
    ИДЗадания = СтруктураФоновогоЗадания.ИдентификаторЗадания;

    // 4. Настраиваем и запускаем ожидание завершения с отображением прогресс-бара
    ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
    ПараметрыОжидания.ВыводитьПрогрессВыполнения = Истина;
    ПараметрыОжидания.Интервал = 2; // Обновлять прогресс каждые 2 секунды

    ДлительныеОперацииКлиент.ОжидатьЗавершение(
        СтруктураФоновогоЗадания,
        Новый ОписаниеОповещения("ОбработатьДанные", ЭтотОбъект),
        ПараметрыОжидания
    );
КонецПроцедуры

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

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

Шаг 2: Запуск фонового задания на сервере (Серверный вызов из модуля формы)

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

  1. Определите имя фонового задания и метод общего модуля, который будет выполнять основную логику.
  2. Используйте ДлительныеОперации.ВыполнитьПроцедуру(), передав ей параметры, включая адрес нашей ТаблицыЗначений.

Пример кода серверной функции в модуле формы:


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

    ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияПроцедуры;
    ПараметрыВыполнения.НаименованиеФоновогоЗадания = НаименованиеЗадания;
    ПараметрыВыполнения.ЗапуститьВФоне = Истина;
    // Можно добавить описание для журнала регистрации
    ПараметрыВыполнения.Описание = "Обработка данных с даты " + Формат(ПараметрыЗапуска.ДатаНачала1, "ДФ=dd.MM.yyyy") +
                                    " по " + Формат(ПараметрыЗапуска.ДатаОкончания1, "ДФ=dd.MM.yyyy");

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

    Возврат СтруктураФоновогоЗадания;
КонецФункции

Шаг 3: Основная логика обработки (Сервер, Общий модуль)

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

  1. Создайте новый общий модуль (например, eyzo_ОбщийМодуль1) в вашем расширении и установите для него флаг "Сервер".
  2. Определите процедуру, соответствующую ВыполняемыйМетод (например, МетодВыполнитьПроцедуру), с теми же параметрами, что были переданы в ДлительныеОперации.ВыполнитьПроцедуру.
  3. Внутри процедуры используйте ПолучитьИзВременногоХранилища() для получения ТаблицыЗначений.
  4. Реализуйте цикл обработки данных. В каждой итерации или через определенные промежутки времени вызывайте ДлительныеОперации.СообщитьПрогресс() для обновления индикатора.

Пример кода в общем модуле eyzo_ОбщийМодуль1:


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

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

    КоличествоВсего = ТаблицаДляОбработки.Количество();
    ОбработаноСтрок = 0;
    ПредыдущийПроцент = 0;

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

        // Для целей демонстрации, имитируем некоторую задержку
        Для Инд = 1 По 1000 Цикл КонецЦикла; 

        ОбработаноСтрок = ОбработаноСтрок + 1;
        ПроцентВыполнения = Окр(ОбработаноСтрок * 100 / КоличествоВсего, 0);

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

        // Также можно проверять необходимость прерывания операции пользователем
        Если ДлительныеОперации.ВыполнениеПрервано() Тогда
            ДлительныеОперации.СообщитьПрогресс(ПроцентВыполнения, "Операция прервана пользователем.");
            Возврат;
        КонецЕсли;
    КонецЦикла;

    // Сообщаем 100% завершение после цикла
    ДлительныеОперации.СообщитьПрогресс(100, "Обработка успешно завершена!");

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

Шаг 4: Ожидание и обработка результата на клиенте

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


&НаКлиенте
Процедура ОбработатьДанные(Результат, ДополнительныеПараметры) Экспорт
    ЭтаФорма.РезультатВыполнения = Результат.Статус; // Результат.Статус содержит строку "Успех" или "Ошибка"

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

Дополнительные рекомендации и важные моменты

При работе с длительными операциями и передачей данных следует учитывать несколько дополнительных аспектов:

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

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

← На главную