При разработке на платформе 1С:Предприятие программисты часто сталкиваются с ситуацией: необходимо обработать тяжелый файл (например, загрузить большой массив данных из CSV) без зависания интерфейса пользователя. Идеальным решением кажется использование фоновых заданий. Однако на практике возникает классическая проблема: на клиенте файл читается, передается на сервер, сохраняется во временную папку, запускается фоновое задание, но внутри него система выдает ошибку, что файла нет или его невозможно прочитать.
Рассмотрим эту ситуацию подробнее, выясним причину такого поведения системы и разберем по шагам рабочие способы решения данной задачи.
Очень часто программисты пишут примерно следующий код для запуска обработки в фоне:
&НаСервере
Процедура ЗагрузитьДанныеНаСервере(АдресВХранилище, РасширениеФайла)
ФайлВременногоХранилища = ПолучитьИзВременногоХранилища(АдресВХранилище);
ИмяФайла = ПолучитьИмяВременногоФайла(РасширениеФайла);
ФайлВременногоХранилища.Записать(ИмяФайла);
ПараметрыФоновогоЗадания = Новый Массив;
ПараметрыФоновогоЗадания.Добавить(ИмяФайла); // Передаем путь к файлу на диске
// Файл существует до запуска фона
ВыбФайл = Новый Файл(ИмяФайла);
Сущ = ВыбФайл.Существует(); // Возвращает Истина
ФоновыеЗадания.Выполнить("МодульЗагрузки.ОбработатьФайл", ПараметрыФоновогоЗадания);
КонецПроцедуры
А внутри процедуры фонового задания мы получаем ошибку. Метод Существует() возвращает Ложь. Куда пропадает файл?
Разберем основные причины, из-за которых возникает эта ошибка:
ПолучитьИмяВременногоФайла, система создает файл в локальной папке Temp того физического сервера, где обрабатывается текущий вызов. Но когда мы запускаем метод ФоновыеЗадания.Выполнить, менеджер кластера может решить запустить это задание на другом рабочем сервере для балансировки нагрузки. Естественно, на жестком диске второго сервера по указанному пути файла не существует.Посмотрим на пример решения, которое устраняет проблему с разными серверами в кластере. Параметры, которые мы передаем в фоновое задание, сериализуются платформой и передаются в новый сеанс независимо от того, на каком узле он стартовал. Этим можно воспользоваться, передав сам контент файла, а не путь к нему.
Вместо передачи строкового пути к файлу, передадим объект ДвоичныеДанные:
&НаСервере
Процедура ЗагрузитьДанныеНаСервере(АдресВХранилище)
// Получаем двоичные данные файла напрямую из хранилища
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(АдресВХранилище);
ПараметрыФоновогоЗадания = Новый Массив;
ПараметрыФоновогоЗадания.Добавить(ДвоичныеДанныеФайла); // Передаем содержимое
ФоновыеЗадания.Выполнить("МодульЗагрузки.ОбработатьФайл", ПараметрыФоновогоЗадания);
КонецПроцедуры
Теперь в самом фоновом задании мы принимаем эти данные, сохраняем их во временный файл уже в контексте того сервера, где запустился фон, и спокойно работаем:
Процедура ОбработатьФайл(ДвоичныеДанныеФайла) Экспорт
// Создаем файл там, где сейчас выполняется фоновое задание
ИмяФайла = ПолучитьИмяВременногоФайла("csv");
ДвоичныеДанныеФайла.Записать(ИмяФайла);
// Проверяем наличие
ВыбФайл = Новый Файл(ИмяФайла);
Если ВыбФайл.Существует() Тогда
// Читаем и обрабатываем файл
КонецЕсли;
КонецПроцедуры
Это решение гарантирует доставку файла в память процесса. Разобраться в других особенностях клиент-серверной передачи данных поможет руководство по работе с файлами в управляемых формах 8.3.
Если файл слишком большой, и вы боитесь передавать его напрямую в массиве параметров, можно использовать ВременноеХранилище. Но делать это нужно правильно.
Чтобы данные из хранилища не удалились и были доступны в фоновом задании, нужно поместить их туда с привязкой к УникальныйИдентификатор, а сам этот идентификатор (или адрес хранилища) передать в фон:
&НаСервере
Процедура ЗагрузитьДанныеНаСервере()
// ... получение данных ...
УИД_Задания = Новый УникальныйИдентификатор;
// Помещаем данные с привязкой к конкретному УИД
АдресДанных = ПоместитьВоВременноеХранилище(МоиДвоичныеДанные, УИД_Задания);
ПараметрыФоновогоЗадания = Новый Массив;
ПараметрыФоновогоЗадания.Добавить(АдресДанных);
// Запускаем задание с тем же УИД!
ФоновыеЗадания.Выполнить("МодульЗагрузки.ОбработатьФайл", ПараметрыФоновогоЗадания, УИД_Задания);
КонецПроцедуры
В фоновом задании мы вызываем ПолучитьИзВременногоХранилища по переданному адресу и получаем наши данные. Для правильной организации длительных операций и получения их результатов без БСП можно использовать пример реализации фоновых заданий с отслеживанием прогресса.
Рассмотрим административный метод решения проблемы. Если вы не можете переписать код конфигурации, можно заставить все серверы кластера использовать единую общую папку для временных файлов.
В свойствах кластера 1С (через консоль администрирования) есть настройка "Каталог временных файлов кластера серверов". Если вы укажете там общий сетевой UNC-путь (например, \\shared-server\1c_temp), доступный всем рабочим серверам с правами на чтение и запись, то метод ПолучитьИмяВременногоФайла() всегда будет возвращать путь именно в этой сетевой папке. В таком случае файл, сохраненный одним сервером, всегда будет найден фоновым заданием на другом сервере.