Как заполнить табличную часть обработки из фонового задания в 1С:Предприятии?

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия Управленческий учет Торговля и дистрибуция
← К списку

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

Почему нельзя просто передать форму в фоновое задание?

Прежде чем перейти к решению, нам необходимо понять, почему прямой путь здесь не работает. Автор вопроса справедливо отметил: **"В фоновое задание я не могу передать форму для её заполнения, так как платформа не может её сериализовать."** Давайте проанализируем эту ситуацию. Формы в 1С:Предприятии являются **клиентскими объектами**. Они содержат интерактивные элементы управления, привязанные к пользовательскому интерфейсу, который доступен только на стороне клиента. Фоновые же задания, наоборот, выполняются на **сервере** в отдельных, независимых системных сеансах. В этих серверных сеансах отсутствует какой-либо контекст пользовательского интерфейса. Когда мы пытаемся передать параметр в фоновое задание, платформа пытается его **сериализовать**, то есть преобразовать в поток байтов для передачи между различными контекстами (клиент-сервер, или внутри серверных сеансов). Объекты форм, содержащие ссылки на элементы управления и клиентские контексты, просто не поддерживают эту операцию. Они не могут быть корректно сериализованы и, следовательно, переданы на сервер для использования в фоновом задании. Таким образом, вся логика, связанная с непосредственным взаимодействием с элементами формы (например, прямое заполнение табличной части формы), должна выполняться на клиенте. Наша задача – найти способ безопасно передать данные с сервера на клиент после завершения фонового задания.

Основной подход: Передача данных через временное хранилище

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

  1. Шаг 1: Подготовка на стороне сервера (в фоновом задании)

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

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

    Рассмотрим пример серверной процедуры, которая будет выполняться в фоновом задании:

    
    // Общий модуль, доступный на сервере
    Процедура ВыполнитьДлительнуюОперациюИСохранитьРезультат(ПараметрыОперации) Экспорт
        // 1. Выполняем длительную операцию по отбору документов
        ПереченьДокументов = Новый ТаблицаЗначений;
        ПереченьДокументов.Колонки.Добавить("Ссылка");
        ПереченьДокументов.Колонки.Добавить("Дата");
        ПереченьДокументов.Колонки.Добавить("Номер");
        // ... Добавьте другие необходимые колонки ...
    
        Запрос = Новый Запрос;
        Запрос.Текст = 
        "ВЫБРАТЬ
        |   ДокументРеализацияТоваровУслуг.Ссылка КАК Ссылка,
        |   ДокументРеализацияТоваровУслуг.Дата КАК Дата,
        |   ДокументРеализацияТоваровУслуг.Номер КАК Номер
        |ИЗ
        |   Документ.РеализацияТоваровУслуг КАК ДокументРеализацияТоваровУслуг
        |ГДЕ
        |   НЕ ДокументРеализацияТоваровУслуг.ПометкаУдаления
        |   И НЕ ДокументРеализацияТоваровУслуг.Проведен"; // Пример условия
    
        РезультатЗапроса = Запрос.Выполнить();
        Выборка = РезультатЗапроса.Выбрать();
    
        Пока Выборка.Следующий() Цикл
            // Здесь можно добавить проверку на связанные документы, 
            // как было упомянуто в исходной задаче
            // Если ПроверитьОтсутствиеСвязанныхДокументов(Выборка.Ссылка) Тогда
                НоваяСтрока = ПереченьДокументов.Добавить();
                НоваяСтрока.Ссылка = Выборка.Ссылка;
                НоваяСтрока.Дата = Выборка.Дата;
                НоваяСтрока.Номер = Выборка.Номер;
            // КонецЕсли;
        КонецЦикла;
    
        // 2. Помещаем полученную ТаблицуЗначений во временное хранилище
        АдресХранилища = ПоместитьВоВременноеХранилище(ПереченьДокументов, УникальныйИдентификатор); // УникальныйИдентификатор - уникальный ключ
                
        // 3. Сохраняем адрес хранилища, чтобы клиент мог его получить
        // Например, в параметрах фонового задания или в ХранилищеОбщихНастроек
        // Для простоты, здесь мы можем вернуть его как результат фонового задания
        // Или, если фоновое задание не возвращает результат напрямую, 
        // использовать ХранилищеОбщихНастроек
        ХранилищеОбщихНастроек.Сохранить(АдресХранилища, "АдресРезультатаФоновогоЗадания_" + УникальныйИдентификаторПользователя);
    
    КонецПроцедуры
    

    Обратите внимание на использование ХранилищеОбщихНастроек.Сохранить(). Это удобный способ сохранить адрес хранилища, чтобы клиент мог получить его позже, даже если фоновое задание не возвращает прямых результатов или нам нужно отслеживать прогресс.

  2. Шаг 2: Запуск фонового задания на клиенте

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

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

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

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

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

    Важно помнить, что ОбработчикОжидания должен быть отключен, когда его работа больше не нужна, чтобы избежать ненужной нагрузки на систему.

  4. Шаг 4: Очистка временного хранилища (опционально)

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

    Например, в процедуре ПроверитьСтатусФоновогоЗадания после успешного получения данных мы можем добавить:

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

Улучшение пользовательского опыта: Индикация прогресса

Пользователю важно видеть, что операция выполняется, а не зависла. Мы можем добавить индикацию прогресса. * **Прогресс в фоновом задании:** Внутри серверной процедуры фонового задания мы можем периодически сообщать о прогрессе, используя метод ФоновоеЗадание.УстановитьПрогресс(Процент). * **Чтение прогресса на клиенте:** В процедуре ПроверитьСтатусФоновогоЗадания мы можем получать процент выполнения из объекта состояния фонового задания (СтатусЗадания.ПроцентВыполнения) и отображать его пользователю.

Использование Библиотеки Стандартных Подсистем (БСП)

Если ваша конфигурация использует **Библиотеку Стандартных Подсистем (БСП)**, то многие аспекты работы с фоновыми заданиями значительно упрощаются. БСП предоставляет готовые механизмы для запуска длительных операций в фоне, управления ими, отслеживания прогресса и уведомления клиента. * **ДлительныеОперации.ЗапуститьВыполнениеВФоне**: Этот метод является стандартным способом запуска фоновых заданий с поддержкой индикации. Он берет на себя всю рутину по созданию фонового задания, передаче параметров и связыванию его с пользовательским интерфейсом. * **ДлительныеОперации.СообщитьПрогресс и ДлительныеОперации.ПолучитьПрогресс**: Эти функции позволяют легко организовать отображение индикации выполнения длительных операций, автоматически обновляя окно ожидания на клиенте. * **Автоматическое получение результатов**: При использовании БСП результаты фонового задания могут быть автоматически получены на клиенте после его завершения, избавляя нас от ручной работы с ОбработчикомОжидания и ХранилищемОбщихНастроек. Если БСП доступна, мы настоятельно рекомендуем изучить ее механизмы, так как они значительно сокращают время разработки и повышают надежность решения. Пример использования БСП:


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

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

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

    // БСП автоматически поместит возвращаемое значение в хранилище и вернет адрес
    Возврат ПереченьДокументов; 
КонецПроцедуры

Общие принципы и рекомендации

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

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

Альтернативные подходы к отображению данных

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

← К списку