Мы рассмотрим, как создать прогресс-бар для длительной операции (5-20 минут) во встроенной обработке 1С, не блокируя пользовательский интерфейс. Особое внимание уделим одной из самых частых проблем — корректной передаче больших объемов данных, таких как таблица значений, из клиентской формы на сервер для обработки в фоновом режиме. Наши решения будут основываться на возможностях платформы 8.3.18.1334, конфигурации Бухгалтерия Предприятия 3.0.95.24 и Библиотеки Стандартных Подсистем (БСП) 3.1.3.494.
Для операций, занимающих значительное время, прямой вызов серверной процедуры блокирует клиентский интерфейс, делая работу пользователя невозможной. Подсистема "Длительные операции" из БСП предоставляет шаблон дополнительной обработки и элегантный механизм для выполнения задач на сервере в фоновом режиме, сохраняя при этом отзывчивость клиентского приложения и позволяя отображать индикатор выполнения.
Она идеально подходит для нашей задачи, поскольку позволяет:
Особенно это актуально для указанных версий платформы и БСП, где этот механизм уже хорошо отлажен и широко используется.
Одна из основных трудностей, с которой часто сталкиваются разработчики, — это передача крупных объектов, таких как ТаблицаЗначений, из клиентского контекста на сервер для обработки в фоновом задании. Прямая передача таких объектов в качестве параметров процедуры может быть неэффективной, замедлять работу или даже приводить к ошибкам из-за ограничений на размер передаваемых данных по сети.
Для решения этой проблемы в 1С предусмотрен механизм временного хранилища. Этот механизм позволяет помещать данные на сервер, получать их адрес и затем использовать этот адрес для доступа к данным из любой точки серверного контекста, включая фоновые задания.
Давайте рассмотрим подробный пример реализации, основанный на коде, который обсуждался на форуме, и дополним его необходимыми элементами для успешной передачи ТаблицыЗначений. Мы будем использовать расширение и общий модуль для серверной логики.
В модуле вашей формы, при нажатии на кнопку, мы должны собрать все необходимые параметры, включая ТаблицуЗначений, и подготовить их для передачи в фоновое задание. Ключевым моментом здесь является помещение ТаблицыЗначений во временное хранилище.
ТаблицуЗначений на форме. Предположим, что она хранится в свойстве Объект.ТЗмоя.ПоместитьВоВременноеХранилище() для сохранения ТаблицыЗначений на сервере. Эта функция вернет уникальный адрес, который мы и передадим в фоновое задание.Пример кода клиентского модуля формы:
&НаКлиенте
Процедура НачатьВыполнение(Команда)
ЭтаФорма.РезультатВыполнения = "";
// 1. Подготовка параметров для фонового задания
ПараметрыЗапуска = Новый Структура;
ПараметрыЗапуска.Вставить("ДатаНачала1", НачалоДня(ЭтаФорма.ДатаНачала));
ПараметрыЗапуска.Вставить("ДатаОкончания1", КонецДня(ЭтаФорма.ДатаОкончания));
// 2. Помещаем ТаблицуЗначений во временное хранилище
// УникальныйИдентификаторФормы нужен для идентификации владельца хранилища
// и автоматического удаления при закрытии формы.
АдресТЗ = ПоместитьВоВременноеХранилище(Объект.ТЗмоя, УникальныйИдентификатор);
ПараметрыЗапуска.Вставить("АдресТЗ", АдресТЗ); // Передаем адрес ТЗ
// 3. Запускаем фоновое задание на сервере
СтруктураФоновогоЗадания = ВыполнитьФоновоеЗаданиеНаСервере(ПараметрыЗапуска, УникальныйИдентификатор);
ИДЗадания = СтруктураФоновогоЗадания.ИдентификаторЗадания;
// 4. Настраиваем и запускаем ожидание завершения с отображением прогресс-бара
ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
ПараметрыОжидания.ВыводитьПрогрессВыполнения = Истина;
ПараметрыОжидания.Интервал = 2; // Обновлять прогресс каждые 2 секунды
ДлительныеОперацииКлиент.ОжидатьЗавершение(
СтруктураФоновогоЗадания,
Новый ОписаниеОповещения("ОбработатьДанные", ЭтотОбъект),
ПараметрыОжидания
);
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьДанные(Результат, ДополнительныеПараметры) Экспорт
// Обработчик, который будет вызван после завершения фонового задания
ЭтаФорма.РезультатВыполнения = Результат.Статус;
// Если ТаблицаЗначений больше не нужна, можно удалить ее из временного хранилища.
// В данном примере, поскольку при ПомещенииВоВременноеХранилище был указан
// УникальныйИдентификатор формы, данные будут автоматически удалены при закрытии формы.
// Если бы УникальныйИдентификатор не передавался, или для более явного контроля,
// потребовалось бы явное УдалитьИзВременногоХранилища(АдресТЗ).
КонецПроцедуры
Эта функция, вызываемая из клиентского модуля формы, отвечает за создание и инициализацию фонового задания с использованием механизмов БСП.
ДлительныеОперации.ВыполнитьПроцедуру(), передав ей параметры, включая адрес нашей ТаблицыЗначений.Пример кода серверной функции в модуле формы:
&НаСервере
Функция ВыполнитьФоновоеЗаданиеНаСервере(ПараметрыЗапуска, УникальныйИдентификаторФормы)
НаименованиеЗадания = "Запуск длительной операции обработки данных";
// Указываем полный путь к процедуре в общем модуле, которая будет выполнять работу
ВыполняемыйМетод = "eyzo_ОбщийМодуль1.МетодВыполнитьПроцедуру";
ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияПроцедуры;
ПараметрыВыполнения.НаименованиеФоновогоЗадания = НаименованиеЗадания;
ПараметрыВыполнения.ЗапуститьВФоне = Истина;
// Можно добавить описание для журнала регистрации
ПараметрыВыполнения.Описание = "Обработка данных с даты " + Формат(ПараметрыЗапуска.ДатаНачала1, "ДФ=dd.MM.yyyy") +
" по " + Формат(ПараметрыЗапуска.ДатаОкончания1, "ДФ=dd.MM.yyyy");
// Передаем параметры, включая АдресТЗ, в выполняемый метод
СтруктураФоновогоЗадания = ДлительныеОперации.ВыполнитьПроцедуру(
ПараметрыВыполнения,
ВыполняемыйМетод,
ПараметрыЗапуска.ДатаНачала1,
ПараметрыЗапуска.ДатаОкончания1,
ПараметрыЗапуска.АдресТЗ // Передаем адрес ТаблицыЗначений
);
Возврат СтруктураФоновогоЗадания;
КонецФункции
Эта процедура будет выполняться в фоновом задании на сервере. Здесь мы должны получить ТаблицуЗначений по переданному адресу и выполнить необходимую обработку, периодически обновляя прогресс.
eyzo_ОбщийМодуль1) в вашем расширении и установите для него флаг "Сервер".ВыполняемыйМетод (например, МетодВыполнитьПроцедуру), с теми же параметрами, что были переданы в ДлительныеОперации.ВыполнитьПроцедуру.ПолучитьИзВременногоХранилища() для получения ТаблицыЗначений.ДлительныеОперации.СообщитьПрогресс() для обновления индикатора.Пример кода в общем модуле eyzo_ОбщийМодуль1:
&НаСервере
Процедура МетодВыполнитьПроцедуру(ДатаНачала, ДатаОкончания, АдресТЗ) Экспорт
// 1. Получаем ТаблицуЗначений из временного хранилища
ТаблицаДляОбработки = ПолучитьИзВременногоХранилища(АдресТЗ);
// Если ТаблицаЗначений не получена или пуста, прекращаем выполнение
Если ТаблицаДляОбработки = Неопределено Или ТаблицаДляОбработки.Количество() = 0 Тогда
ДлительныеОперации.СообщитьПрогресс(100, "Нет данных для обработки.");
Возврат;
КонецЕсли;
КоличествоВсего = ТаблицаДляОбработки.Количество();
ОбработаноСтрок = 0;
ПредыдущийПроцент = 0;
Для Каждого СтрокаТЗ Из ТаблицаДляОбработки Цикл
// Здесь размещаем вашу логику обработки каждой строки ТаблицыЗначений
// Например, выполнение каких-либо расчетов, запись в базу данных и т.д.
// Имитация длительной операции. В реальной задаче здесь будет выполняться
// полезное действие, которое занимает время.
// Пример: ВыполнитьЗапросКБазеДанных(СтрокаТЗ.Поле);
// Или: ЗаписатьДокумент(СтрокаТЗ.СсылкаНаДокумент);
// Для целей демонстрации, имитируем некоторую задержку
Для Инд = 1 По 1000 Цикл КонецЦикла;
ОбработаноСтрок = ОбработаноСтрок + 1;
ПроцентВыполнения = Окр(ОбработаноСтрок * 100 / КоличествоВсего, 0);
// Обновляем прогресс только при изменении процента, чтобы избежать слишком частых вызовов
Если ПроцентВыполнения > ПредыдущийПроцент Тогда
ДлительныеОперации.СообщитьПрогресс(ПроцентВыполнения, "Обработано " + ОбработаноСтрок + " из " + КоличествоВсего + " строк.");
ПредыдущийПроцент = ПроцентВыполнения;
КонецЕсли;
// Также можно проверять необходимость прерывания операции пользователем
Если ДлительныеОперации.ВыполнениеПрервано() Тогда
ДлительныеОперации.СообщитьПрогресс(ПроцентВыполнения, "Операция прервана пользователем.");
Возврат;
КонецЕсли;
КонецЦикла;
// Сообщаем 100% завершение после цикла
ДлительныеОперации.СообщитьПрогресс(100, "Обработка успешно завершена!");
// Если необходимо вернуть какой-либо результат на клиент (например, обработанные данные),
// его также можно поместить во временное хранилище и передать адрес
// через ДополнительныеПараметры в СтруктуруФоновогоЗадания.
// Например:
// РезультатОбработки = "Некий текстовый результат или структура";
// АдресРезультата = ПоместитьВоВременноеХранилище(РезультатОбработки);
// ДлительныеОперации.ЗавершитьВыполнение(АдресРезультата);
КонецПроцедуры
После того как фоновое задание завершится, вызовется клиентская процедура-оповещение (в нашем случае ОбработатьДанные). Здесь мы можем проанализировать результат выполнения и, что очень важно, очистить временное хранилище, если данные больше не нужны. Код этой процедуры уже был приведен в Шаге 1, но мы еще раз подчеркнем момент с очисткой временного хранилища.
&НаКлиенте
Процедура ОбработатьДанные(Результат, ДополнительныеПараметры) Экспорт
ЭтаФорма.РезультатВыполнения = Результат.Статус; // Результат.Статус содержит строку "Успех" или "Ошибка"
// Очень важно: Удаление ТаблицыЗначений из временного хранилища.
// В нашем примере, при ПомещенииВоВременноеХранилище был указан
// УникальныйИдентификатор формы как владелец. Это означает, что данные
// автоматически удалятся при закрытии формы, к которой они привязаны.
//
// Если бы владелец не был указан, или для более явного управления ресурсами
// в других сценариях, потребовалось бы явное вызов УдалитьИзВременногоХранилища(АдресТЗ).
// Для этого АдресТЗ нужно было бы либо сохранить в переменной формы, либо
// передать обратно из фонового задания через ДополнительныеПараметры.
//
// Например, если бы мы передали АдресТЗ в ДополнительныеПараметры при вызове ДлительныеОперации.ЗавершитьВыполнение:
// Если ДополнительныеПараметры <> Неопределено И ДополнительныеПараметры.Свойство("АдресТЗДляУдаления") Тогда
// УдалитьИзВременногоХранилища(ДополнительныеПараметры.АдресТЗДляУдаления);
// КонецЕсли;
КонецПроцедуры
При работе с длительными операциями и передачей данных следует учитывать несколько дополнительных аспектов:
УникальныйИдентификатор формы), то данные автоматически удаляются при закрытии формы. Однако, для явного контроля или в других сценариях (например, если данные помещаются без владельца или связаны с объектом, который не будет закрыт), рекомендуется явно вызывать УдалитьИзВременногоХранилища() после того, как данные перестанут быть нужны. Это помогает избежать утечек памяти на сервере.ДлительныеОперации предоставляет механизмы для обработки ошибок. В случае возникновения исключения в серверной процедуре, оно будет передано в клиентский обработчик оповещения. Также в серверной процедуре можно реализовать логику проверки ДлительныеОперации.ВыполнениеПрервано(), чтобы позволить пользователю остановить фоновое задание.ТаблицаЗначений содержит очень большое количество строк (сотни тысяч, миллионы), рассмотрите альтернативные подходы:
ЭтаФорма.ОбновитьОтображениеДанных() и периодическим вызовом серверной функции. Этот подход менее предпочтителен для длительных операций, так как хоть и обновляет индикатор, но все равно блокирует клиентский интерфейс на время выполнения каждого серверного вызова. Он подходит для операций, где каждый шаг серверного вызова очень быстрый, а общая длительность складывается из множества таких шагов. Для операций, требующих длительного непрерывного серверного выполнения, подсистема "Длительные операции" с фоновыми заданиями является более правильным выбором.Мы рассмотрели основной и наиболее эффективный способ реализации прогресс-бара с использованием подсистемы Длительные операции БСП и корректной передачей ТаблицыЗначений, что является стандартной практикой для обеспечения неблокирующего пользовательского интерфейса в 1С:Предприятии.