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