Как передать файлы, созданные фоновым заданием на сервере 1С, на терминальный сервер пользователя?

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

При разработке высокопроизводительных механизмов выгрузки данных на платформе «1С:Предприятие 8» разработчики часто прибегают к параллельной обработке с использованием фоновых заданий. Это логично: распределение генерации тяжелых файлов (например, отчетов в формате CSV объемом в миллионы строк) по нескольким фоновым потокам позволяет в разы сократить время выполнения задачи. Однако в реальной клиент-серверной архитектуре и распределенной ИТ-инфраструктуре возникает серьезное аппаратное и логическое препятствие. Фоновые задания физически исполняются на сервере приложений 1С (или даже на сервере СУБД, если это комбинированная схема), а пользователь запускает обработку из своей сессии на одном из нескольких терминальных серверов (RDP).

В результате файлы, успешно созданные фоновыми заданиями, остаются заблокированными в локальной файловой системе сервера 1С. Попытки передать огромные объемы данных через память, используя сериализацию (например, метод ЗначениеВСтрокуВнутр()), закономерно приводят к критическому исчерпанию оперативной памяти и падению рабочего процесса с ошибкой "bad allocation". В этой статье мы подробно разберем причины возникновения проблемы и рассмотрим несколько надежных архитектурных способов организации обмена файлами между сервером 1С и клиентскими терминалами.

Почему возникает ошибка "Bad Allocation" и почему нельзя гонять файлы через память

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

Если файл содержит 1.5 миллиона строк, то объем занимаемой памяти в текстовом представлении внутри XML или внутреннего формата 1С увеличивается в несколько раз. Даже на производительных 64-разрядных серверах рабочий процесс rphost не всегда может выделить столь гигантский непрерывный кусок RAM, что и провоцирует аварийное завершение процесса с ошибкой "bad allocation" (ошибка выделения памяти).

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

Рассмотрим по шагам основные и наиболее эффективные варианты решения проблемы передачи файлов.

Решение 1. Использование штатного механизма "Временное хранилище"

Платформа «1С:Предприятие 8» предоставляет стандартный, безопасный и изолированный механизм передачи данных между родительским сеансом пользователя и порожденным им фоновым заданием. Этот способ работает «из коробки» и не требует дополнительных настроек прав администрирования операционной системы.

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

Разберем пример кода реализации данного механизма.

Шаг 1. Код в модуле управляемой формы (клиент/сервер родительского сеанса):


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

Шаг 2. Код серверного общего модуля (выполняется в фоновом задании):


Процедура СформироватьФайлыФоновое(АдресХранилища) Экспорт
    
    // Шаг 2.1. Получаем имя временного файла на сервере приложений
    ИмяФайла = ПолучитьИмяВременногоФайла("csv");
    
    // Шаг 2.2. Наполняем файл данными (потоковая запись в файл)
    Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);
    Для Счетчик = 1 По 1500000 Цикл
        Запись.ЗаписатьСтроку(Строка(Счетчик) + ";Пример данных;Параметр");
    КонецЦикла;
    Запись.Закрыть();
    
    // Шаг 2.3. Преобразуем файл в ДвоичныеДанные без загрузки текста в ОЗУ как строки
    ДанныеФайла = Новый ДвоичныеДанные(ИмяФайла);
    
    // Шаг 2.4. Помещаем двоичные данные по переданному адресу. 
    // После этого они автоматически станут доступны родительскому сеансу после завершения задания.
    ПоместитьВоВременноеХранилище(ДанныеФайла, АдресХранилища);
    
    // Очищаем временный файл на диске сервера
    УдалитьФайлы(ИмяФайла);
    
КонецПроцедуры

Шаг 3. Получение готового файла в родительской сессии (после успешного завершения фонового задания):


&НаКлиенте
Процедура ЗабратьФайлИзХранилища(АдресХранилища)
    
    // Получаем файл из временного хранилища на клиентскую машину (или на терминальный сервер)
    ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресХранилища);
    Если ТипЗнч(ДвоичныеДанные) = Тип("ДвоичныеДанные") Тогда
        ПутьНаТерминале = "C:\Users\Public\Documents\FinalResult.csv";
        ДвоичныеДанные.Записать(ПутьНаТерминале);
        ПоказатьПредупреждение(, "Файл успешно передан и сохранен по пути: " + ПутьНаТерминале);
    КонецЕсли;
    
КонецПроцедуры

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

Решение 2. Динамическое подключение сетевой папки (шары) через COM-объект

Если специфика архитектуры требует физического сохранения частей файлов на общем сетевом ресурсе, но 1С «не видит» сетевую папку, проблема кроется в правах операционной системы Windows.

Служба сервера 1С (как правило, имеющая имя USR1CV8 или USR1CV82) запускается под локальной системной учетной записью, которая не авторизована в домене Active Directory и не имеет прав доступа к сетевым ресурсам других серверов.

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

Для реализации этого подхода используется системный COM-объект WScript.Network:


// Выполняется на сервере приложений 1С в фоновом задании
Процедура КопироватьФайлНаСетевойРесурс(ЛокальныйПутьКФайлу, ИмяЦелевогоФайла) Экспорт
    
    СетевойРесурс = "\\10.215.20.10\PUB"; // Адрес общей папки
    ИмяПользователяДомена = "domen\userdomen"; // Логин доменного пользователя с правами
    ПарольПользователя = "Qw1234567"; // Пароль учетной записи
    
    // Создаем объект для работы с сетевым окружением Windows
    NetworkObject = Новый COMОбъект("WScript.Network");
    
    // Подключаем сетевую папку как локальный диск "L:" с передачей авторизации
    Попытка 
        NetworkObject.MapNetworkDrive("L:", СетевойРесурс, Ложь, ИмяПользователяДомена, ПарольПользователя);
    Исключение
        ЗаписатьЛогСобытий("Не удалось примонтировать сетевой диск: " + ОписаниеОшибки());
        Возврат;
    КонецПопытки;
    
    // Копируем файл средствами 1С на подключенный диск L:
    ПутьНаСетевомДиске = "L:\" + ИмяЦелевогоФайла;
    Попытка
        КопироватьФайл(ЛокальныйПутьКФайлу, ПутьНаСетевомДиске);
    Исключение
        ЗаписатьЛогСобытий("Ошибка при копировании файла: " + ОписаниеОшибки());
    КонецПопытки;
    
    // Обязательно отключаем сетевой диск после завершения операции, чтобы освободить ресурсы ОС
    Попытка
        NetworkObject.RemoveNetworkDrive("L:", Истина, Ложь);
    Исключение
        // Игнорируем или логируем ошибку размонтирования
    КонецПопытки;
    
    NetworkObject = Неопределено;
    
КонецПроцедуры

Преимущества метода:

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

Важное ограничение: при одновременной работе нескольких фоновых заданий на одном сервере приложений может возникнуть конфликт использования буквы диска L:. Поэтому рекомендуется динамически подбирать свободную букву диска в операционной системе или использовать UNC-пути, если это позволяет конфигурация прав.

Решение 3. Использование Базы Данных (СУБД) как транзитного хранилища

Если инфраструктурные ограничения Active Directory не позволяют использовать сетевые папки, а временное хранилище использовать нежелательно из-за риска обрыва сеанса клиента, можно задействовать СУБД в качестве «моста».

Для этого в конфигурации создается служебный справочник или регистр сведений с ресурсом типа ХранилищеЗначения.

Разберем этот алгоритм по шагам:

  1. Каждое фоновое задание формирует свой кусок файла локально на жестком диске сервера приложений 1С.
  2. По завершении формирования файл считывается в объект ДвоичныеДанные и записывается в созданную запись регистра сведений (в поле с типом ХранилищеЗначения).
  3. Поскольку у всех серверов кластера 1С и терминальных серверов по умолчанию есть постоянный доступ к общей базе данных SQL, им не требуются сетевые права операционной системы.
  4. Родительский сеанс на терминальном сервере считывает записи из регистра, извлекает двоичные данные, сохраняет их на диск терминала, после чего производит конкатенацию (объединение) файлов и удаляет временные записи из СУБД.

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

← На главную