Почему ПолучитьИзВременногоХранилища в фоновом задании возвращает Неопределено и как это исправить

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

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

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

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

Когда пользователь работает в системе (через тонкий, толстый или веб-клиент), для него создается уникальный сеанс. Если мы помещаем файл во временное хранилище, адрес вида e1cib/tempstorage/... будет действителен только внутри этого конкретного сеанса.

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

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

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

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

Рассмотрим, как правильно реализовать этот подход на практике:


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

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

Способ 2. Использование временного файла на сервере (для тяжелых данных и файлов)

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

Разберем данный алгоритм по шагам:

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

Посмотрим на пример программного кода, реализующего этот паттерн:


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

Теперь проанализируем код процедуры-обработчика фонового задания в общем модуле:


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

Способ 3. Передача сложных структур через базу данных

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

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

Обратная задача: как вернуть результат работы из фонового задания

Довольно часто требуется решить обратную задачу: фоновое задание должно выполнить тяжелые расчеты (например, собрать сложный отчёт) и вернуть результат в родительский сеанс пользователя.

Для реализации этого сценария в платформе 1С реализовано штатное исключение из правил изоляции сеансов. Для этого используется особый двухэтапный механизм адресации:

  1. В родительском сеансе (обязательно в контексте формы) мы создаем пустой адрес во временном хранилище, явно связывая его с уникальным идентификатором формы: АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, ЭтаФорма.УникальныйИдентификатор);
  2. Передаем этот строковый АдресРезультата в качестве параметра при запуске фонового задания.
  3. Внутри фонового задания мы производим вычисления и помещаем готовый результат прямо по этому адресу: ПоместитьВоВременноеХранилище(РезультатВычислений, АдресРезультата);
  4. Важная деталь поведения платформы: Данные, помещенные фоновым заданием по этому адресу, сразу же становятся недоступными для чтения внутри самого фонового процесса. В родительском же сеансе они станут доступны только тогда, когда фоновое задание полностью завершит свою работу (перейдет в статус Выполнено).
  5. После успешного завершения задания в родительском сеансе мы считываем готовый результат: Результат = ПолучитьИзВременногоХранилища(АдресРезультата);

Критичные нюансы времени жизни данных (второй параметр)

Даже если мы работаем в рамках одного сеанса и не используем фоновые задания, функция ПолучитьИзВременногоХранилища может неожиданно вернуть Неопределено. Это происходит из-за недооценки важности второго параметра метода ПоместитьВоВременноеХранилище(<Данные>, <Адрес/УИД>):

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

← На главную