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