Сбои при работе с временным хранилищем в платформе «1С:Предприятие 8.3» — одна из тех проблем, которая может годами не проявлять себя в стабильно работающем коде, но внезапно заявить о себе после очередного обновления платформы или изменения конфигурации сервера. Особенно часто разработчики сталкиваются с ситуацией, когда метод ПолучитьИзВременногоХранилища() неожиданно возвращает Неопределено при попытке передать данные между фоновыми или регламентными заданиями. Давайте подробно разберем, почему это происходит, как устроены механизмы платформы и какими способами можно гарантированно решить эту проблему.
В практике разработки часто встречается следующий паттерн: регламентное задание запускает асинхронную процедуру (фоновое задание). Перед запуском родительский сеанс помещает структуру параметров во временное хранилище с помощью метода ПоместитьВоВременноеХранилище(), получает адрес и передает его в фоновое задание. Фоновое задание в цикле или при старте пытается прочитать данные по этому адресу. До определенного момента эта схема может успешно работать, но после обновления платформы (например, на релизы ветки 8.3.13 и выше) или при переносе базы на другой сервер код начинает "иногда" давать сбои, возвращая пустое значение.
Проанализируем причины такого поведения. В старых релизах платформы существовала недокументированная особенность: если фоновое задание запускалось в рамках одного и того же рабочего процесса сервера (rphost), что и родительский сеанс регламентного задания, они могли разделять общий контекст оперативной памяти. В этом случае данные из временного хранилища одного сеанса "случайно" считывались в другом сеансе. Однако при многопроцессной архитектуре (когда в кластере активно несколько процессов rphost) или при ужесточении изоляции сеансов в новых версиях платформы эта лазейка закрылась. Платформа 1С стала строго изолировать сеансы друг от друга, делая невозможным "нелегальный" доступ к чужому временному хранилищу.
Каждое фоновое задание в 1С — это абсолютно отдельный сеанс со своим уникальным идентификатором. Регламентное задание — это также отдельный сеанс. Когда регламентное задание запускает фоновое, мы имеем дело с двумя разными сеансами. Согласно официальной документации 1С, передача данных через временное хранилище между разными сеансами жестко регламентирована.
Рассмотрим подробнее три режима времени жизни данных во временном хранилище в зависимости от переданных параметров в метод ПоместитьВоВременноеХранилище(Значение, Адрес/УникальныйИдентификатор):
Новый УникальныйИдентификатор(), платформа привязывает данные к текущему сеансу пользователя (или сеансу регламентного задания). По документации эти данные должны жить до завершения сеанса. Однако, поскольку фоновое задание выполняется в *другом* сеансе, оно не имеет прямого доступа к этим данным. Более того, платформа оптимизирует использование памяти, и менеджер сеансовых данных может очистить эти "осиротевшие" данные гораздо раньше (например, через 20 минут неактивности или при первом же делении рабочих процессов).Таким образом, попытка прочитать данные из временного хранилища одного сеанса внутри другого сеанса (фонового задания) без использования явной привязки к форме — это архитектурная ошибка. Платформа не гарантирует сохранность этих данных, и поведение системы становится непредсказуемым.
Если объем передаваемых данных невелик (например, структура настроек, небольшая таблица значений или массив идентификаторов), самым простым и надежным решением будет полный отказ от временного хранилища. Мы можем передавать сериализуемые данные напрямую в качестве параметров при запуске фонового задания.
Разберем по шагам, как это реализовать в коде:
// В родительском сеансе (например, в модуле регламентного задания)
ПараметрыЗапуска = Новый Структура;
ПараметрыЗапуска.Вставить("Организация", СсылкаНаОрганизацию);
ПараметрыЗапуска.Вставить("Период", ТекущаяДата());
ПараметрыЗапуска.Вставить("МассивОбъектов", ПолучитьМассивДляОбработки());
ПараметрыПроцедуры = Новый Массив;
ПараметрыПроцедуры.Добавить(ПараметрыЗапуска);
// Запуск фонового задания с передачей параметров напрямую
ФоновыеЗадания.Выполнить("МойОбщийМодуль.ВыполнитьОбработкуДанных", ПараметрыПроцедуры);
При таком подходе данные сериализуются платформой и гарантированно передаются в контекст нового сеанса фонового задания. Исключается любая вероятность получения ошибки Неопределено.
Если объем передаваемых данных велик (например, тяжелые таблицы значений с тысячами строк, сложные деревья или структуры), передача их через параметры может негативно сказаться на производительности из-за затрат на сериализацию в момент старта задания. В этом случае лучшим решением будет использование промежуточной (транзитной) таблицы в базе данных — служебного регистра сведений.
Проанализируем алгоритм реализации этого способа:
БуферОбменаФоновыхЗаданий.КлючСвязи с типом УникальныйИдентификатор.Данные с типом ХранилищеЗначения.ХранилищеЗначения и запишем в регистр под новым уникальным идентификатором.Посмотрим на пример реализации записи данных в родительском сеансе:
// Код в родительском сеансе
КлючСвязи = Новый УникальныйИдентификатор();
ХранилищеДанных = Новый ХранилищеЗначения(МояБольшаяТаблицаЗначений);
// Запись в транзитный регистр сведений
МенеджерЗаписи = РегистрыСведений.БуферОбменаФоновыхЗаданий.СоздатьМенеджерЗаписи();
МенеджерЗаписи.КлючСвязи = КлючСвязи;
МенеджерЗаписи.Данные = ХранилищеДанных;
МенеджерЗаписи.Записать(Истина);
// Передаем только КлючСвязи в фоновое задание
ПараметрыПроцедуры = Новый Массив;
ПараметрыПроцедуры.Добавить(КлючСвязи);
ФоновыеЗадания.Выполнить("МойОбщийМодуль.ОбработатьБольшиеДанные", ПараметрыПроцедуры);
Теперь рассмотрим, как фоновое задание должно прочитать эти данные и очистить за собой буфер:
// Код в общем модуле (выполняется в фоновом задании)
Процедура ОбработатьБольшиеДанные(КлючСвязи) Экспорт
// Получаем запись из регистра
Отбор = Новый Структура("КлючСвязи", КлючСвязи);
Выборка = РегистрыСведений.БуферОбменаФоновыхЗаданий.Выбрать(Отбор);
Если Выборка.Следующий() Тогда
// Извлекаем реальные данные из хранилища значений
ИсходныеДанные = Выборка.Данные.Получить();
// Удаляем временную запись из базы данных, чтобы избежать утечки памяти
МенеджерЗаписи = РегистрыСведений.БуферОбменаФоновыхЗаданий.СоздатьМенеджерЗаписи();
МенеджерЗаписи.КлючСвязи = КлючСвязи;
МенеджерЗаписи.Удалить();
// Выполняем полезную работу с ИсходныеДанные
ОбработатьТаблицу(ИсходныеДанные);
Иначе
ЗаписатьВЛогОшибок("Данные для обработки не найдены в регистре сведений по ключу: " + КлючСвязи);
КонецЕсли;
КонецПроцедуры
Этот способ является наиболее надежным и отказоустойчивым. Данные гарантированно запишутся в СУБД, и фоновое задание сможет получить к ним доступ независимо от того, на каком рабочем сервере или в каком процессе кластера оно запустилось.
Если вам необходимо передавать очень большие объемы информации, которые нецелесообразно записывать в базу данных (например, архивы, файлы выгрузки XML/JSON, медиа-файлы или тяжелые отчеты), можно использовать механизм временных файлов.
При использовании этого метода крайне важно учитывать особенности работы кластера 1С. Если ваш кластер состоит из нескольких рабочих серверов, фоновое задание может быть запущено на другом физическом компьютере, отличном от того, на котором выполнялся родительский сеанс. Поэтому путь к файлу должен вести на общий сетевой ресурс (UNC-путь), к которому имеют доступ учетные записи операционной системы, под которыми запущены службы 1С:Предприятия на всех серверах кластера.
Рассмотрим по шагам схему реализации:
ПолучитьИмяВременногоФайла().УдалитьФайлы().Если вы работаете в конфигурации, созданной на базе Библиотеки стандартных подсистем (БСП), вам не нужно вручную изобретать велосипеды для управления фоновыми заданиями. В БСП встроен мощный и надежный механизм — общий модуль ДлительныеОперации.
Этот механизм инкапсулирует в себе все сложности взаимодействия сеансов, корректно передает параметры, отслеживает состояние выполнения заданий и обеспечивает безопасный возврат результатов обратно в родительский поток через временное хранилище, используя правильные механизмы привязки к уникальным идентификаторам формы. Использование стандартных методов ДлительныеОперации.ВыполнитьВФоне() убережет ваш код от ошибок при последующих обновлениях платформы.
Подводя итог нашему анализу, сформулируем основные правила безопасной работы с временным хранилищем в «1С:Предприятии 8.3»:
ФоновыеЗадания.Выполнить().Соблюдение этих простых архитектурных правил позволит создавать стабильные, масштабируемые и отказоустойчивые асинхронные алгоритмы, которые будут успешно работать на любых версиях платформы 1С:Предприятие.