При разработке на платформе 1С:Предприятие часто возникает задача выноса тяжелых и длительных операций в фоновые задания, чтобы не блокировать интерфейс пользователя. Типичный сценарий: пользователь выбирает на клиенте несколько файлов, они загружаются на сервер, а затем запускается фоновое задание для их обработки (например, парсинга, распознавания или загрузки в базу данных). Однако на этапе реализации разработчики часто сталкиваются с тем, что внутри фонового задания метод ПолучитьИзВременногоХранилища возвращает Неопределено по переданным адресам. Давайте подробно разберем, почему так происходит, и рассмотрим рабочие способы решения этой проблемы.
Для начала проанализируем архитектуру работы платформы 1С. Главная причина проблемы кроется в изоляции сеансов. Временное хранилище жестко связано с текущим сеансом пользователя. Когда мы вызываем метод НачатьПомещениеФайловНаСервер и привязываем файлы к УникальныйИдентификатор формы, эти данные помещаются в оперативную память и временные файлы, ассоциированные именно с сеансом текущего пользователя.
Когда мы запускаем фоновое задание через метод ФоновыеЗадания.Выполнить, сервер 1С создает для него абсолютно новый, отдельный фоновый сеанс. Этот сеанс выполняется изолированно. У него свой собственный контекст, свои параметры и, что самое главное, свое изолированное временное хранилище. Фоновое задание физически не имеет доступа к данным временного хранилища родительского сеанса, даже если мы передадим туда правильные строковые адреса вида "e1cib/tempstorage/...". Платформа просто не сможет сопоставить этот адрес в новом сеансе и вернет нам значение Неопределено.
Важное примечание: Существует единственный штатный механизм работы с временным хранилищем «между сеансами» — это передача результата работы из фонового задания обратно в родительский сеанс. Для этого в родительском сеансе регистрируется пустой адрес во временном хранилище, передается в фоновое задание, фоновое задание записывает туда результат, и после его завершения родительский сеанс может считать данные. Но передать данные в обратном направлении — из родительского сеанса в фоновое задание через адрес — напрямую невозможно.
Разберем подробно 4 практических способа решения этой задачи, их преимущества и недостатки.
---
Если файлы имеют небольшой объем, самым простым и логичным решением будет передача объектов ДвоичныеДанные напрямую в массиве параметров при запуске фонового задания.
Разберем по шагам, как это реализовать:
ФоновыеЗадания.Выполнить.Посмотрим на пример кода подготовки данных на сервере:
&НаСервере
Процедура ЗапуститьОбработкуФайловНаСервере(МассивАдресовВременногоХранилища)
МассивДвоичныхДанных = Новый Массив();
Для Каждого Адрес Из МассивАдресовВременногоХранилища Цикл
// Извлекаем двоичные данные из хранилища текущего сеанса
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(Адрес);
Если ТипЗнч(ДвоичныеДанныеФайла) = Тип("ДвоичныеДанные") Тогда
МассивДвоичныхДанных.Добавить(ДвоичныеДанныеФайла);
КонецЕсли;
КонецЦикла;
// Передаем массив непосредственно как параметр фонового задания
ПараметрыЗапуска = Новый Массив();
ПараметрыЗапуска.Добавить(МассивДвоичныхДанных);
ФоновыеЗадания.Выполнить("МодульОбработкиФайлов.ВыполнитьЗагрузку", ПараметрыЗапуска);
КонецПроцедуры
В самом фоновом задании код будет выглядеть максимально просто:
// В общем модуле "МодульОбработкиФайлов" (серверный, вызов сервера)
Процедура ВыполнитьЗагрузку(МассивДвоичныхДанных) Экспорт
Для Каждого ФайлДанные Из МассивДвоичныхДанных Цикл
// Здесь мы уже имеем готовые ДвоичныеДанные без обращения к временному хранилищу
// Например, можем прочитать их или обработать:
// ЧтениеТекста = Новый ЧтениеТекста; ...
КонецЦикла;
КонецПроцедуры
Преимущества способа: Крайне простая реализация, отсутствие необходимости работать с файловой системой сервера и СУБД.
Недостатки и риски: Этот метод категорически не подходит для больших файлов (от десятков мегабайт и выше). При передаче параметров в фоновое задание платформа производит их сериализацию в формат XML/XDTO. Если файлы объемные, сериализация вызовет лавинообразный рост потребления оперативной памяти рабочим процессом сервера 1С (rphost), что может привести к критическому падению сервера или аварийному завершению сеансов пользователей.
---
Для того чтобы обойти проблему с перерасходом оперативной памяти при сериализации, мы можем использовать жесткий диск сервера в качестве транзитного хранилища. Мы сохраняем файлы из временного хранилища на диск, а в фоновое задание передаем только строковые пути к этим файлам.
Рассмотрим подробнее реализацию этого алгоритма:
&НаСервере
Процедура ЗапуститьОбработкуЧерезФайлы(МассивАдресовВременногоХранилища)
МассивПутейКФайлам = Новый Массив();
Для Каждого Адрес Из МассивАдресовВременногоХранилища Цикл
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(Адрес);
Если ТипЗнч(ДвоичныеДанныеФайла) = Тип("ДвоичныеДанные") Тогда
// Получаем уникальное имя временного файла на сервере
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("tmp");
// Записываем данные на диск сервера
ДвоичныеДанныеФайла.Записать(ИмяВременногоФайла);
МассивПутейКФайлам.Добавить(ИмяВременногоФайла);
КонецЕсли;
КонецЦикла;
ПараметрыЗапуска = Новый Массив();
ПараметрыЗапуска.Добавить(МассивПутейКФайлам);
ФоновыеЗадания.Выполнить("МодульОбработкиФайлов.ОбработатьФайлыСДиска", ПараметрыЗапуска);
КонецПроцедуры
В коде фонового задания мы считываем файлы по путям, обрабатываем их и обязательно очищаем за собой диск, чтобы не засорять систему:
Процедура ОбработатьФайлыСДиска(МассивПутейКФайлам) Экспорт
Для Каждого ПутьКФайлу Из МассивПутейКФайлам Цикл
ФайлНаДиске = Новый Файл(ПутьКФайлу);
Если ФайлНаДиске.Существует() Тогда
// Выполняем чтение и обработку файла
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлу);
// Выполняем логику обработки...
// Обязательно удаляем временный файл после обработки!
Попытка
УдалитьФайлы(ПутьКФайлу);
Исключение
ЗаписьЖурналаРегистрации("ОбработкаФайлов", УровеньЖурналаРегистрации.Предупреждение,,,,,
"Не удалось удалить временный файл: " + ПутьКФайлу);
КонецПопытки;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Важное предупреждение для многосерверных кластеров! Если ваш сервер 1С работает в режиме кластера, состоящего из нескольких физических рабочих серверов (компьютеров), этот способ в базовом варианте может дать сбой. Метод ПолучитьИмяВременногоФайла() запишет файл на локальный диск того сервера, на котором выполнялся родительский сеанс. А само фоновое задание диспетчер кластера может запустить на совершенно другом рабочем сервере, где этого файла физически не окажется.
Решение для кластера: Вместо локального пути используйте общую сетевую папку по стандарту UNC (например, "\\SharedServer\TempFolder\file.tmp"), к которой имеют одинаковый сетевой доступ на чтение и запись системные учетные записи службы агента 1С (обычно это пользователь usr1cv8) на всех серверах вашего кластера.
---
Если вы хотите получить максимально надежное, отказоустойчивое и независимое от конфигурации серверов решение, следует использовать базу данных в качестве посредника. Это стандартный подход для промышленных систем на платформе 1С.
Проанализируем шаги реализации:
Справочник.ВременныеФайлыФоновыхЗаданий) или используем функционал Библиотеки Стандартных Подсистем (БСП) «Присоединенные файлы». В этом справочнике должен быть реквизит с типом ХранилищеЗначения для хранения двоичных данных.Рассмотрим пример кода:
&НаСервере
Процедура ЗапуститьОбработкуЧерезБД(МассивАдресовВременногоХранилища)
МассивСсылок = Новый Массив();
Для Каждого Адрес Из МассивАдресовВременногоХранилища Цикл
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(Адрес);
Если ТипЗнч(ДвоичныеДанныеФайла) = Тип("ДвоичныеДанные") Тогда
// Создаем временный объект в базе данных
НовыйФайлОбъект = Справочники.ВременныеФайлы.СоздатьЭлемент();
НовыйФайлОбъект.Наименование = "ВременныйФайл_" + Строка(Новый УникальныйИдентификатор);
НовыйФайлОбъект.ДанныеФайла = Новый ХранилищеЗначения(ДвоичныеДанныеФайла, Новый СжатиеДанных(9));
НовыйФайлОбъект.Записать();
МассивСсылок.Добавить(НовыйФайлОбъект.Ссылка);
КонецЕсли;
КонецЦикла;
ПараметрыЗапуска = Новый Массив();
ПараметрыЗапуска.Добавить(МассивСсылок);
ФоновыеЗадания.Выполнить("МодульОбработкиФайлов.ОбработатьИзБазыДанных", ПараметрыЗапуска);
КонецПроцедуры
Код фонового задания для обработки и очистки базы:
Процедура ОбработатьИзБазыДанных(МассивСсылок) Экспорт
Для Каждого СсылкаНаФайл Из МассивСсылок Цикл
// Получаем двоичные данные из СУБД
ДвоичныеДанныеФайла = СсылкаНаФайл.ДанныеФайла.Получить();
Если ДвоичныеДанныеФайла <> Неопределено Тогда
// Обрабатываем файл...
КонецЕсли;
// Удаляем временный элемент базы данных
ВременныйОбъект = СсылкаНаФайл.ПолучитьОбъект();
Если ВременныйОбъект <> Неопределено Тогда
Попытка
ВременныйОбъект.Удалить();
Исключение
ЗаписьЖурналаРегистрации("ОбработкаФайлов", УровеньЖурналаРегистрации.Предупреждение,,,,,
"Не удалось удалить временный элемент справочника: " + Строка(СсылкаНаФайл));
КонецПопытки;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Преимущества способа: Полная независимость от структуры кластера серверов 1С и сетевых папок. Данные гарантированно будут доступны на любом рабочем сервере кластера 1С, так как они считываются напрямую из общей базы данных. Безопасно для оперативной памяти.
Недостатки: Создает дополнительную транзакционную нагрузку на СУБД за счет операций записи и последующего физического удаления больших двоичных данных (LOB/BLOB).
---
Этот способ является наиболее современным развитием архитектуры корпоративных систем. Если в вашей компании развернуто собственное S3-совместимое хранилище (например, MinIO, Яндекс Облако и др.), вы можете выгружать файлы туда напрямую с клиента или через родительский сеанс сервера 1С, а в фоновое задание передавать только ссылки-токены (или пути в бакете) для скачивания файлов.
Преимущества: Полная разгрузка СУБД и серверов 1С от хранения тяжелых временных файлов. Идеальное масштабирование системы.
---
Подведем итоги анализа и определимся, какой метод выбрать в вашей конкретной ситуации:
ПолучитьИмяВременногоФайла()).