Как выполнять длительные операции по обработке данных табличных частей обработки в фоновом режиме, не блокируя интерфейс пользователя в 1С:Предприятии 8.3?

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

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

Понимание типов модулей 1С для фоновых операций

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

  1. Модуль объекта обработки (или документа, справочника):

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

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

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

  2. Модуль менеджера объекта:

    Модуль менеджера содержит статические методы, которые относятся ко всему типу объектов (например, ко всему справочнику "Номенклатура"), а не к конкретному экземпляру. Мы можем думать о нем как об общем модуле, который метаданными "привязан" к определенному типу объекта. Его особенности:

    • Отсутствие контекста: Модуль менеджера не имеет прямого доступа к реквизитам конкретного объекта. Для работы с данными объекта ему необходимо явно передавать объект или его ссылку в качестве параметра.
    • Статические методы: Методы модуля менеджера вызываются без создания экземпляра объекта, например, Справочники.Номенклатура.ПолучитьНаименование().
    • Ограничение для внешних обработок: У внешних обработок отсутствует модуль менеджера. Это важное ограничение, если мы разрабатываем внешние обработки.

    Модуль менеджера подходит для операций, не требующих прямого контекста конкретного объекта или работающих со множеством объектов одного типа.

  3. Общий модуль:

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

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

Механизм длительных операций и фоновых заданий в 1С

Для выполнения длительных операций без блокировки пользовательского интерфейса в 1С используется механизм фоновых заданий. В типовых конфигурациях, основанных на Библиотеке стандартных подсистем (БСП), для управления фоновыми заданиями используются общие модули ДлительныеОперации и ДлительныеОперацииКлиент.

Эти модули предоставляют унифицированный подход для:

  1. Запуска фоновых заданий.
  2. Отслеживания их прогресса.
  3. Обработки результатов по завершении.

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

Ключевая проблема: Сериализация данных и временное хранилище

Основное препятствие при передаче данных в фоновые задания – требование сериализуемости всех параметров. Табличные части (ТЧ) объектов 1С, а также сам объект обработки/документа, не являются сериализуемыми напрямую.

Для обхода этого ограничения используется механизм временного хранилища с помощью функций ПоместитьВоВременноеХранилище() и ПолучитьИзВременногоХранилища().

  1. Как это работает:

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

  2. Получение данных:

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

  3. Обработка табличных частей:

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

  4. Влияние на производительность:

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

Пошаговая реализация длительной операции с табличной частью обработки

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

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


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

В данном примере ОбработкаОбъект.СоздатьНовуюНоменклатуруСервер() в модуле объекта напрямую работает с табличной частью Объект.ТЧ. Наша задача – перенести эту логику в фон.

Шаг 1: Рефакторинг основной логики в параметризованную процедуру

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

Мы создадим новый общий модуль (например, МояОбработкаБэкграунд) и в нем разместим нашу процедуру. Эта процедура больше не будет зависеть от Объект.ТЧ, а будет получать ТаблицуЗначений и настройки как параметры.


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

Обратите внимание, что процедура СоздатьНовуюНоменклатуруФоново принимает два адреса временного хранилища: один для входных данных и один для выходных. Также внутри нее происходит работа с ТаблицейЗначений (ТЗНоменклатура), а не напрямую с табличной частью объекта.

Шаг 2: Запуск фонового задания из формы обработки

Теперь мы изменим процедуру в модуле формы, которая вызывается по кнопке, чтобы она запускала фоновое задание.


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

// Вспомогательная функция для помещения во временное хранилище с сервера
&НаСервере
Функция ПоместитьВоВременноеХранилищеНаСервере(Данные)
    Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции

В этом коде мы видим несколько важных моментов:

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

Шаг 3: Обработка результата по завершении фонового задания

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


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

// Вспомогательная функция для получения из временного хранилища с сервера
&НаСервере
Функция ПолучитьРезультатыИзВременногоХранилищаНаСервере(Адрес)
    Возврат ПолучитьИзВременногоХранилища(Адрес);
КонецФункции

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

Унификация кода для регламентных заданий и интерактивного использования

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

  1. Для интерактивного фонового выполнения:

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

  2. Для регламентных заданий:

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

    Если в регламентном задании используется сама обработка (например, если она хранится в справочнике "ДополнительныеОтчетыИОбработки"), мы можем сделать так:

    
    // В модуле регламентного задания или в процедуре, вызываемой регламентным заданием
    Процедура ВыполнитьСозданиеНоменклатурыРегламентом() Экспорт
        
        // 1. Создаем экземпляр обработки
        Обработка = Обработки.МояОбработка.Создать();
        
        // 2. Заполняем реквизиты обработки (настройки)
        Обработка.ВидНоменклатурыПоУмолчанию = Справочники.ВидыНоменклатуры.НайтиПоКоду("00001");
        // ... Заполняем другие настройки
        
        // 3. Заполняем табличную часть обработки из какого-либо источника (например, запрос к базе)
        // Обработка.ТЧ.Загрузить(ДанныеИзЗапроса);
        // Или используем уже существующие данные
        
        // 4. Выгружаем ТЧ обработки в ТаблицуЗначений для передачи в общий модуль
        ТЗНоменклатураДляФона = Обработка.ТЧ.Выгрузить();
        
        // 5. Формируем входные параметры для общего модуля
        ПараметрыВхода = Новый Структура;
        ПараметрыВхода.Вставить("ТаблицаНоменклатуры", ТЗНоменклатураДляФона);
        ПараметрыВхода.Вставить("ВидНоменклатурыПоУмолчанию", Обработка.ВидНоменклатурыПоУмолчанию);
        ПараметрыВхода.Вставить("ЕдиницаИзмеренияПоУмолчанию", Обработка.ВидНоменклатурыПоУмолчанию.ЕдиницаИзмерения);
        // ...
        
        // 6. Помещаем параметры во временное хранилище
        АдресВходныхДанных = ПоместитьВоВременноеХранилище(ПараметрыВхода, УникальныйИдентификатор);
        АдресВыходныхДанных = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
        
        // 7. Вызываем процедуру из общего модуля напрямую
        МояОбработкаБэкграунд.СоздатьНовуюНоменклатуруФоново(АдресВходныхДанных, АдресВыходныхДанных);
        
        // 8. Получаем результаты из хранилища (если нужно для дальнейшей обработки в регламенте)
        Результаты = ПолучитьИзВременногоХранилища(АдресВыходныхДанных);
        Если Результаты <> Неопределено Тогда
            // Обработка результатов регламентного задания
            // Например, запись логов, отправка уведомлений
        КонецЕсли;
        
    КонецПроцедуры
    

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

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

Особенности внешних обработок

Как мы уже упоминали, у внешних обработок нет модуля менеджера. Если вы разрабатываете внешнюю обработку и хотите использовать механизм длительных операций, вам нужно будет:

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

Заключение

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

← На главную