При разработке на платформе 1С:Предприятие 8 программисты часто сталкиваются с ситуацией, когда код, успешно работающий в обычном контексте (например, на клиенте или в обычном серверном вызове), начинает вести себя непредсказуемо при переносе вычислений в фоновые задания. Типичный пример такой проблемы — получение значения Неопределено при попытке извлечь файл или структуру данных с помощью функции ПолучитьИзВременногоХранилища внутри фонового процесса. Давайте подробно разберем, почему так происходит, как устроена изоляция сеансов в 1С и какими способами можно обойти это системное ограничение.
Для начала проанализируем архитектуру работы платформы 1С с памятью. Временное хранилище — это специализированный быстрый кэш на сервере приложений, предназначенный для краткосрочного хранения данных. Главная его особенность заключается в том, что оно жестко привязано к текущему сеансу пользователя.
Когда пользователь работает в системе (через тонкий, толстый или веб-клиент), для него создается уникальный сеанс. Если мы помещаем файл во временное хранилище, адрес вида e1cib/tempstorage/... будет действителен только внутри этого конкретного сеанса.
Фоновое задание, в свою очередь, запускается сервером приложений 1С в новом, полностью изолированном сеансе. Поскольку этот сеанс никак не связан с родительским сеансом пользователя, фоновое задание физически не имеет доступа к его временному хранилищу. Попытка прочитать адрес хранилища родительского сеанса из кода фонового задания всегда будет возвращать Неопределено.
Интересно отметить, что в тонком клиенте при тестировании разработчик может не сразу заметить проблему, если вызовы происходят синхронно. Однако при переходе на веб-клиент или при первом же асинхронном запуске фонового процесса ограничение сеансовой изоляции проявляется в полной мере.
Если нам требуется передать в фоновое задание относительно небольшой объем информации (например, структуру настроек, массив идентификаторов документов или небольшую таблицу значений), нам вообще не нужно использовать временное хранилище. Мы можем передать эти данные напрямую в качестве параметров запуска фонового задания.
Рассмотрим, как правильно реализовать этот подход на практике:
// Код в родительском сеансе (например, в модуле формы)
&НаСервере
Процедура ЗапуститьОбработкуНаСервере()
// Формируем структуру с параметрами
ПараметрыАлгоритма = Новый Структура;
ПараметрыАлгоритма.Вставить("Организация", Объект.Организация);
ПараметрыАлгоритма.Вставить("Период", Объект.ПериодРегистрации);
ПараметрыАлгоритма.Вставить("ИсключатьНеактивные", Истина);
// Помещаем структуру в массив параметров для фонового задания
ПараметрыЗапуска = Новый Массив;
ПараметрыЗапуска.Добавить(ПараметрыАлгоритма);
// Запускаем фоновое задание, передавая параметры напрямую
ФоновыеЗадания.Выполнить("МойОбщийМодуль.ВыполнитьДлительнуюОперацию", ПараметрыЗапуска);
КонецПроцедуры
Платформа автоматически сериализует переданные в массиве объекты, перенесет их через границу сеансов и воссоздаст в контексте фонового задания. Это самый простой, быстрый и безопасный способ передачи метаданных и небольших коллекций.
Когда речь идет о передаче файлов большого объема (например, загружаемых пользователем Excel-таблиц, картинок, тяжелых XML-выгрузок), передавать их напрямую через параметры не рекомендуется, так как это может вызвать лавинообразный расход оперативной памяти процессами сервера приложений. В этом случае правильным архитектурным решением будет использование дискового пространства сервера.
Разберем данный алгоритм по шагам:
ПолучитьИмяВременногоФайла().Посмотрим на пример программного кода, реализующего этот паттерн:
// Код в родительском сеансе на сервере
&НаСервере
Процедура ЗапуститьИмпортФайла(АдресФайлаВХранилище)
// Получаем двоичные данные файла, загруженного пользователем
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресФайлаВХранилище);
// Создаем временный файл на диске сервера приложений
ПутьКВременномуФайлу = ПолучитьИмяВременногоФайла("xlsx");
ДвоичныеДанные.Записать(ПутьКВременномуФайлу);
// Передаем путь к файлу в фоновое задание
ПараметрыЗапуска = Новый Массив;
ПараметрыЗапуска.Добавить(ПутьКВременномуФайлу);
ФоновыеЗадания.Выполнить("МойОбщийМодуль.ИмпортироватьФайлФоном", ПараметрыЗапуска);
КонецПроцедуры
Теперь проанализируем код процедуры-обработчика фонового задания в общем модуле:
// Код в серверном общем модуле (свойства: Сервер, Вызов сервера)
Процедура ИмпортироватьФайлФоном(ПутьКФайлу) Экспорт
// Проверяем существование файла на диске
ФайлНаДиске = Новый Файл(ПутьКФайлу);
Если Не ФайлНаДиске.Существует() Тогда
ЗаписьЖурналаРегистрации("ИмпортДанных", УровеньЖурналаРегистрации.Ошибка,,, "Файл не найден на сервере: " + ПутьКФайлу);
Возврат;
КонецЕсли;
// Выполняем чтение и обработку данных
Попытка
ТабличныйДокумент = Новый ТабличныйДокумент;
ТабличныйДокумент.Прочитать(ПутьКФайлу);
// Тут располагается логика разбора таблицы и записи данных в БД...
Исключение
ЗаписьЖурналаРегистрации("ИмпортДанных", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
// Обязательно очищаем за собой временные ресурсы
Попытка
УдалитьФайлы(ПутьКФайлу);
Исключение
// Логируем ошибку удаления, если файл оказался заблокирован процессом
ЗаписьЖурналаРегистрации("ИмпортДанных", УровеньЖурналаРегистрации.Предупреждение,,, "Не удалось удалить файл: " + ПутьКФайлу);
КонецПопытки;
КонецПроцедуры
В случаях, когда структуры данных слишком сложны для простой сериализации, или когда требуется гарантировать сохранность данных даже в случае перезапуска рабочего процесса сервера 1С, целесообразно использовать базу данных в качестве посредника.
Мы можем временно записать массив или структуру данных в специально созданный справочник или регистр сведений (например, РегистрСведений.ОчередьЗадач), передать в фоновое задание только ссылку или ключ записи, а внутри фонового задания считать эти данные и удалить временную запись после успешного завершения операции.
Довольно часто требуется решить обратную задачу: фоновое задание должно выполнить тяжелые расчеты (например, собрать сложный отчёт) и вернуть результат в родительский сеанс пользователя.
Для реализации этого сценария в платформе 1С реализовано штатное исключение из правил изоляции сеансов. Для этого используется особый двухэтапный механизм адресации:
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, ЭтаФорма.УникальныйИдентификатор);
АдресРезультата в качестве параметра при запуске фонового задания.ПоместитьВоВременноеХранилище(РезультатВычислений, АдресРезультата);
Выполнено).Результат = ПолучитьИзВременногоХранилища(АдресРезультата);
Даже если мы работаем в рамках одного сеанса и не используем фоновые задания, функция ПолучитьИзВременногоХранилища может неожиданно вернуть Неопределено. Это происходит из-за недооценки важности второго параметра метода ПоместитьВоВременноеХранилище(<Данные>, <Адрес/УИД>):
Понимание этих архитектурных особенностей платформы 1С:Предприятие 8 позволит вам проектировать быстрые, надежные и отказоустойчивые алгоритмы, работающие в многопоточном режиме без досадных ошибок потери данных.