Как передать массив файлов во временном хранилище в фоновое задание в 1С?

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

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

Выясняем причину: почему временное хранилище недоступно в фоновом задании?

Для начала проанализируем архитектуру работы платформы 1С. Главная причина проблемы кроется в изоляции сеансов. Временное хранилище жестко связано с текущим сеансом пользователя. Когда мы вызываем метод НачатьПомещениеФайловНаСервер и привязываем файлы к УникальныйИдентификатор формы, эти данные помещаются в оперативную память и временные файлы, ассоциированные именно с сеансом текущего пользователя.

Когда мы запускаем фоновое задание через метод ФоновыеЗадания.Выполнить, сервер 1С создает для него абсолютно новый, отдельный фоновый сеанс. Этот сеанс выполняется изолированно. У него свой собственный контекст, свои параметры и, что самое главное, свое изолированное временное хранилище. Фоновое задание физически не имеет доступа к данным временного хранилища родительского сеанса, даже если мы передадим туда правильные строковые адреса вида "e1cib/tempstorage/...". Платформа просто не сможет сопоставить этот адрес в новом сеансе и вернет нам значение Неопределено.

Важное примечание: Существует единственный штатный механизм работы с временным хранилищем «между сеансами» — это передача результата работы из фонового задания обратно в родительский сеанс. Для этого в родительском сеансе регистрируется пустой адрес во временном хранилище, передается в фоновое задание, фоновое задание записывает туда результат, и после его завершения родительский сеанс может считать данные. Но передать данные в обратном направлении — из родительского сеанса в фоновое задание через адрес — напрямую невозможно.

Разберем подробно 4 практических способа решения этой задачи, их преимущества и недостатки.

---

Способ 1. Передача двоичных данных напрямую в параметрах фонового задания

Если файлы имеют небольшой объем, самым простым и логичным решением будет передача объектов ДвоичныеДанные напрямую в массиве параметров при запуске фонового задания.

Разберем по шагам, как это реализовать:

  1. На клиенте пользователь выбирает файлы и помещает их на сервер.
  2. В процедуре завершения на сервере мы извлекаем двоичные данные файлов по адресам временного хранилища.
  3. Помещаем эти двоичные данные в массив или структуру и передаем как параметр в ФоновыеЗадания.Выполнить.

Посмотрим на пример кода подготовки данных на сервере:


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

В самом фоновом задании код будет выглядеть максимально просто:


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

Преимущества способа: Крайне простая реализация, отсутствие необходимости работать с файловой системой сервера и СУБД.

Недостатки и риски: Этот метод категорически не подходит для больших файлов (от десятков мегабайт и выше). При передаче параметров в фоновое задание платформа производит их сериализацию в формат XML/XDTO. Если файлы объемные, сериализация вызовет лавинообразный рост потребления оперативной памяти рабочим процессом сервера 1С (rphost), что может привести к критическому падению сервера или аварийному завершению сеансов пользователей.

---

Способ 2. Сохранение во временные файлы на диске сервера

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

Рассмотрим подробнее реализацию этого алгоритма:


&НаСервере
Процедура ЗапуститьОбработкуЧерезФайлы(МассивАдресовВременногоХранилища)
    
    МассивПутейКФайлам = Новый Массив();
    
    Для Каждого Адрес Из МассивАдресовВременногоХранилища Цикл
        ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(Адрес);
        Если ТипЗнч(ДвоичныеДанныеФайла) = Тип("ДвоичныеДанные") Тогда
            // Получаем уникальное имя временного файла на сервере
            ИмяВременногоФайла = ПолучитьИмяВременногоФайла("tmp");
            // Записываем данные на диск сервера
            ДвоичныеДанныеФайла.Записать(ИмяВременногоФайла);
            МассивПутейКФайлам.Добавить(ИмяВременногоФайла);
        КонецЕсли;
    КонецЦикла;
    
    ПараметрыЗапуска = Новый Массив();
    ПараметрыЗапуска.Добавить(МассивПутейКФайлам);
    
    ФоновыеЗадания.Выполнить("МодульОбработкиФайлов.ОбработатьФайлыСДиска", ПараметрыЗапуска);
    
КонецПроцедуры

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


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

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

Решение для кластера: Вместо локального пути используйте общую сетевую папку по стандарту UNC (например, "\\SharedServer\TempFolder\file.tmp"), к которой имеют одинаковый сетевой доступ на чтение и запись системные учетные записи службы агента 1С (обычно это пользователь usr1cv8) на всех серверах вашего кластера.

---

Способ 3. Использование промежуточных таблиц СУБД (Регистры, справочники или БСП)

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

Проанализируем шаги реализации:

  1. Создаем временный справочник (например, Справочник.ВременныеФайлыФоновыхЗаданий) или используем функционал Библиотеки Стандартных Подсистем (БСП) «Присоединенные файлы». В этом справочнике должен быть реквизит с типом ХранилищеЗначения для хранения двоичных данных.
  2. Перед запуском фонового задания записываем файлы в этот справочник.
  3. В фоновое задание передаем только массив ссылок на созданные элементы справочника.
  4. Фоновое задание извлекает данные из СУБД, обрабатывает их и в конце удаляет элементы справочника, очищая базу данных.

Рассмотрим пример кода:


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

Код фонового задания для обработки и очистки базы:


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

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

Недостатки: Создает дополнительную транзакционную нагрузку на СУБД за счет операций записи и последующего физического удаления больших двоичных данных (LOB/BLOB).

---

Способ 4. Выгрузка во внешнее объектное хранилище (S3 / WebDAV)

Этот способ является наиболее современным развитием архитектуры корпоративных систем. Если в вашей компании развернуто собственное S3-совместимое хранилище (например, MinIO, Яндекс Облако и др.), вы можете выгружать файлы туда напрямую с клиента или через родительский сеанс сервера 1С, а в фоновое задание передавать только ссылки-токены (или пути в бакете) для скачивания файлов.

Преимущества: Полная разгрузка СУБД и серверов 1С от хранения тяжелых временных файлов. Идеальное масштабирование системы.

---

Резюме и рекомендации по выбору решения

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

← На главную