При разработке интерфейсов в 1С:Предприятие на управляемых формах мы часто сталкиваемся с необходимостью выполнения длительных операций. Стандартный подход подразумевает использование фоновых заданий, чтобы не блокировать интерфейс пользователя. Однако возникает интересная задача: что делать, если мы хотим не просто показать результат в конце ("Вот готовое дерево"), а демонстрировать процесс заполнения таблицы в реальном времени, "анимировать" появление строк, чтобы пользователь видел прогресс не только в виде полоски процентов, но и в виде реальных данных.
Рассмотрим эту задачу подробно. Мы разберем, почему "решение в лоб" не работает, как использовать стандартные механизмы платформы и какие существуют архитектурные хитрости для передачи данных из фона на клиент "на лету".
Начнем с анализа типичной ошибки. Попытка передать ДанныеФормыДерево (реквизит формы) напрямую в фоновое задание обречена на провал. Причина кроется в архитектуре платформы 1С.
Фоновое задание выполняется в отдельном сеансе (или в фоновом контексте текущего сеанса, в зависимости от режима работы базы), который изолирован от контекста формы. Метод РеквизитФормыВЗначение создает объект ДеревоЗначений на сервере, но передать ссылку на "живую" форму или ее данные в фон невозможно, так как параметры фонового задания должны быть сериализуемыми (превращаемыми в строку/XML/JSON для передачи между процессами).
Ошибка "Параметр фонового задания не поддерживает сериализацию" прямо указывает на это ограничение. Фоновое задание ничего не знает о вашей форме, и форма не может напрямую "подглядывать" в память фонового задания.
Если анимация (постепенное появление строк) не является критичным требованием, а задача состоит только в том, чтобы сформировать дерево без блокировки интерфейса и показать его в конце, мы используем механизм временного хранилища. Это самый надежный и правильный метод с точки зрения стандартов 1С.
Алгоритм действий следующий:
ДеревоЗначений.ВременноеХранилище.Пример кода фонового задания:
Процедура ВыполнитьФормированиеДерева(АдресРезультата, Параметры) Экспорт
// Формируем настоящее дерево значений
ДеревоРезультат = Новый ДеревоЗначений;
// ... логика заполнения ...
// Помещаем результат во временное хранилище
// Важно: данные станут доступны родительскому сеансу ТОЛЬКО после завершения задания
ПоместитьВоВременноеХранилище(ДеревоРезультат, АдресРезультата);
КонецПроцедуры
На форме в обработчике завершения:
&НаСервере
Процедура ЗагрузитьРезультат(Адрес)
ДеревоИзХранилища = ПолучитьИзВременногоХранилища(Адрес);
ЗначениеВРеквизитФормы(ДеревоИзХранилища, ДеревоНаФорме);
КонецПроцедуры
Важное ограничение: Данные, помещенные во временное хранилище внутри фонового задания (если мы не передали адрес извне), доступны родительскому сеансу только после успешного завершения фонового задания. Это делает невозможным использование ВременноеХранилище для потоковой передачи данных "по частям" во время выполнения.
Чтобы реализовать постепенное заполнение, нам нужно создать канал связи между фоновым процессом и клиентским приложением, через который можно передавать "порции" данных. Рассмотрим несколько рабочих вариантов.
Это изящный способ, не требующий создания объектов в базе данных. Механизм СообщениеПользователю позволяет отправлять текстовые сообщения из серверного кода (в том числе из фона). На клиенте мы можем перехватывать эти сообщения.
Идея: Фоновое задание сериализует новые строки в JSON и отправляет их как текст сообщения. Клиент периодически опрашивает наличие сообщений, парсит JSON и добавляет строки в дерево.
Шаг 1. Фоновое задание
Процедура ФоновоеЗаполнение(Параметры) Экспорт
Для Каждого ПорцияДанных Из ИсточникДанных Цикл
// 1. Получаем очередную строку данных
СтруктураСтроки = ПолучитьСтруктуруДанных(ПорцияДанных);
// 2. Сериализуем в JSON для компактности
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, СтруктураСтроки);
СтрокаJSON = ЗаписьJSON.Закрыть();
// 3. Отправляем как сообщение
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "DATA:" + СтрокаJSON; // Префикс для фильтрации
Сообщение.Сообщить();
// Небольшая пауза, чтобы не заспамить канал, если данных слишком много
// В реальности лучше накапливать пачку строк и отправлять раз в секунду
КонецЦикла;
КонецПроцедуры
Шаг 2. Клиентская часть (Опрос)
На форме при запуске задания мы должны подключить обработчик ожидания, который будет дергать сервер или проверять сообщения.
&НаКлиенте
Процедура ЗапуститьЗаполнение()
// Запуск фонового...
// ...
// Подключаем "слушателя"
ПодключитьОбработчикОжидания("ОбработатьВходящиеДанные", 1); // Раз в 1 секунду
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьВходящиеДанные()
// Получаем сообщения, предназначенные текущему пользователю/сеансу
// Параметр Истина - удаляет сообщения из очереди после прочтения
МассивСообщений = ПолучитьСообщенияПользователю(Истина);
Для Каждого Сообщ Из МассивСообщений Цикл
Если СтрНачинаетсяС(Сообщ.Текст, "DATA:") Тогда
JSONСтрока = Сред(Сообщ.Текст, 6); // Убираем префикс
ДобавитьСтрокуВДевево(JSONСтрока);
Иначе
// Это обычное сообщение, выводим пользователю
Сообщ.Сообщить();
КонецЕсли;
КонецЦикла;
// Если задание завершено - отключаем обработчик
Если ФоновоеЗаданиеЗавершено Тогда
ОтключитьОбработчикОжидания("ОбработатьВходящиеДанные");
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ДобавитьСтрокуВДевево(JSONСтрока)
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(JSONСтрока);
ДанныеСтроки = ПрочитатьJSON(Чтение);
Чтение.Закрыть();
// Добавляем строку в дерево формы
НоваяСтрока = ДеревоНаФорме.ПолучитьЭлементы().Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ДанныеСтроки);
КонецПроцедуры
В теме форума обсуждался подход с использованием ХранилищеОбщихНастроек. Это своего рода "общая память", доступная и с сервера (фонового), и с клиента. Это рабочий, но архитектурно менее чистый вариант, так как он засоряет базу данных временными настройками, если их корректно не удалить.
Суть метода:
ХранилищеОбщихНастроек.Сохранить("КэшФона", КлючЗадания, Данные).ПодключитьОбработчикОжидания вызывает серверную функцию.ХранилищеОбщихНастроек.Загрузить(...), возвращает их на клиент и очищает хранилище.Главный недостаток — необходимость постоянных серверных вызовов с клиента для проверки наличия новых данных ("polling"). Это создает нагрузку на сервер приложений.
Более надежный вариант для передачи больших объемов данных частями — использование специального служебного РегистрСведений. Это работает аналогично Хранилищу настроек, но дает больше контроля.
КэшОбменаСФоновымиЗаданиями.ИдентификаторЗадания (УникальныйИдентификатор), НомерПорции (Число).ДанныеJSON (Строка неограниченной длины) или ХранилищеЗначения.Фоновое задание пишет записи. Клиент через обработчик ожидания читает записи с номером порции больше, чем последний прочитанный, и удаляет прочитанное.
При реализации "анимации" нужно быть осторожным. Если строк очень много (тысячи), и вы будете обновлять форму на каждую строку, интерфейс может "зависнуть" из-за постоянной перерисовки.
Поэтому проанализируем важные правила оптимизации:
ХранилищеЗначения.Реализовать визуальное заполнение ДеревоЗначений из фона возможно, но это требует выхода за рамки стандартных механизмов передачи результата.