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

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

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

Анализ проблемы: почему код работал годами, а после обновления сломался?

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

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

Как устроена изоляция сеансов и время жизни временного хранилища

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

Рассмотрим подробнее три режима времени жизни данных во временном хранилище в зависимости от переданных параметров в метод ПоместитьВоВременноеХранилище(Значение, Адрес/УникальныйИдентификатор):

  1. Второй параметр не указан: Значение помещается во временное хранилище и привязывается к текущему вызову сервера. Оно гарантированно будет удалено после завершения очередного запроса сервера из общего модуля, при контекстном или неконтекстном серверном вызове из формы, либо при завершении текущей серверной транзакции. Использовать такой адрес для передачи в фоновое задание категорически нельзя — данные исчезнут практически мгновенно.
  2. Второй параметр — УникальныйИдентификатор формы: Данные привязываются к жизненному циклу конкретной клиентской формы. Они будут доступны до тех пор, пока форма открыта, и автоматически удалятся после её закрытия. Это единственный полностью документированный и гарантированный способ передачи данных из фонового задания на клиентскую часть (в форму, которая это задание инициировала).
  3. Второй параметр — Случайный УникальныйИдентификатор (не привязанный к форме): Если мы передаем просто Новый УникальныйИдентификатор(), платформа привязывает данные к текущему сеансу пользователя (или сеансу регламентного задания). По документации эти данные должны жить до завершения сеанса. Однако, поскольку фоновое задание выполняется в *другом* сеансе, оно не имеет прямого доступа к этим данным. Более того, платформа оптимизирует использование памяти, и менеджер сеансовых данных может очистить эти "осиротевшие" данные гораздо раньше (например, через 20 минут неактивности или при первом же делении рабочих процессов).

Таким образом, попытка прочитать данные из временного хранилища одного сеанса внутри другого сеанса (фонового задания) без использования явной привязки к форме — это архитектурная ошибка. Платформа не гарантирует сохранность этих данных, и поведение системы становится непредсказуемым.

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

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

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


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

ПараметрыПроцедуры = Новый Массив;
ПараметрыПроцедуры.Добавить(ПараметрыЗапуска);

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

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

Способ решения 2: Использование транзитного Регистра сведений для тяжелых данных

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

Проанализируем алгоритм реализации этого способа:

  1. Создадим в конфигураторе неподчиненный регистр сведений БуферОбменаФоновыхЗаданий.
  2. Определим в регистре измерение КлючСвязи с типом УникальныйИдентификатор.
  3. Определим ресурс Данные с типом ХранилищеЗначения.
  4. Перед запуском фонового задания упакуем наши данные в ХранилищеЗначения и запишем в регистр под новым уникальным идентификатором.
  5. Передадим этот идентификатор (простую строку или UUID) в параметры фонового задания.
  6. Внутри фонового задания считаем данные из регистра по ключу связи, а после успешного прочтения — удалим запись, чтобы не забивать базу данных.

Посмотрим на пример реализации записи данных в родительском сеансе:


// Код в родительском сеансе
КлючСвязи = Новый УникальныйИдентификатор();
ХранилищеДанных = Новый ХранилищеЗначения(МояБольшаяТаблицаЗначений);

// Запись в транзитный регистр сведений
МенеджерЗаписи = РегистрыСведений.БуферОбменаФоновыхЗаданий.СоздатьМенеджерЗаписи();
МенеджерЗаписи.КлючСвязи = КлючСвязи;
МенеджерЗаписи.Данные = ХранилищеДанных;
МенеджерЗаписи.Записать(Истина);

// Передаем только КлючСвязи в фоновое задание
ПараметрыПроцедуры = Новый Массив;
ПараметрыПроцедуры.Добавить(КлючСвязи);

ФоновыеЗадания.Выполнить("МойОбщийМодуль.ОбработатьБольшиеДанные", ПараметрыПроцедуры);

Теперь рассмотрим, как фоновое задание должно прочитать эти данные и очистить за собой буфер:


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

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

Способ решения 3: Передача данных через временные файлы на сервере

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

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

Рассмотрим по шагам схему реализации:

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

Способ решения 4: Использование механизмов БСП (Длительные операции)

Если вы работаете в конфигурации, созданной на базе Библиотеки стандартных подсистем (БСП), вам не нужно вручную изобретать велосипеды для управления фоновыми заданиями. В БСП встроен мощный и надежный механизм — общий модуль ДлительныеОперации.

Этот механизм инкапсулирует в себе все сложности взаимодействия сеансов, корректно передает параметры, отслеживает состояние выполнения заданий и обеспечивает безопасный возврат результатов обратно в родительский поток через временное хранилище, используя правильные механизмы привязки к уникальным идентификаторам формы. Использование стандартных методов ДлительныеОперации.ВыполнитьВФоне() убережет ваш код от ошибок при последующих обновлениях платформы.

Резюме и правила хорошего тона при работе с временным хранилищем

Подводя итог нашему анализу, сформулируем основные правила безопасной работы с временным хранилищем в «1С:Предприятии 8.3»:

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

← На главную