Ошибка удаления файла PDF: Процесс не может получить доступ к файлу, так как он занят другим процессом. Как решить?

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

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

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

Суть проблемы: блокировка файла процессом

Рассмотрим типичный код, который приводит к ошибке. Мы создаем HTTP-соединение, выполняем запрос и сохраняем результат в локальный файл на диске.


Соединение = Новый HTTPСоединение(Сервер);
Запрос = Новый HTTPЗапрос("/get-report");
ПутьДляСохранения = "C:/Temp/Отчет.pdf";

// Скачиваем файл на диск
ОбещаниеСО = Соединение.ПолучитьАсинх(Запрос, ПутьДляСохранения);
Ответ = Ждать ОбещаниеСО;

Если Ответ.КодСостояния = 200 Тогда
    // Открываем файл для просмотра
    ДокPDF = Новый ДокументPDF;
    ДокPDF.Прочитать("C:/Temp/Отчет.pdf");
    ДокPDF.Показать(,"C:/Temp/Отчет.pdf");
    
    // Пытаемся удалить файл сразу после открытия или позже
    УдалитьФайлы("C:/Temp/", "Отчет.pdf"); // -> ОШИБКА БЛОКИРОВКИ
КонецЕсли;

Когда мы вызываем метод ДокументPDF.Прочитать(), платформа 1С (а точнее, внутренний механизм работы с PDF) захватывает файловый дескриптор. Операционная система помечает файл как "используемый". У объекта ДокументPDF отсутствует явный метод Закрыть(), который принудительно освободил бы файл.

Даже если мы попытаемся очистить переменную:

ДокPDF = Неопределено;

Это не гарантирует мгновенного снятия блокировки. В платформе 1С работает механизм автоматического управления памятью (Garbage Collector). Объект будет уничтожен физически, и файл будет отпущен только тогда, когда "сборщик мусора" решит очистить память. Это может произойти через секунду, минуту или вообще при закрытии приложения. До этого момента файл удалить невозможно.

Неэффективные решения

Давайте проанализируем, что обычно пытаются сделать, и почему это не работает:

  1. Присвоение Неопределено. Как сказано выше, ссылка удаляется, но объект в памяти еще жив, и блокировка висит.
  2. Закрытие окна просмотра. Метод Показать() открывает визуальную форму. Даже если закрыть это окно, ссылка на файл может удерживаться платформой до перезапуска сеанса.
  3. Предварительное удаление. Удалить файл до его открытия невозможно, так как нам нужно его показать. Удалить после открытия невозможно из-за блокировки.

Правильное решение: Использование потоков в памяти

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

Логика решения следующая:

  1. Получаем ответ от HTTP-сервиса не как файл, а как ДвоичныеДанные.
  2. Открываем из этих двоичных данных Поток.
  3. Скармливаем этот поток объекту ДокументPDF.

В этом случае файловая система диска не задействуется, блокировать нечего, и удалять ничего не нужно. Память освободится автоматически, когда закроется объект.

Пример реализации через поток

Давайте перепишем наш исходный код, используя этот подход. Обратите внимание, что мы убираем параметр пути сохранения из метода получения запроса.


Соединение = Новый HTTPСоединение(Сервер);
Запрос = Новый HTTPЗапрос("/get-report");

// 1. Получаем ответ, но не сохраняем его сразу в файл
ОбещаниеСО = Соединение.ПолучитьАсинх(Запрос); // Путь не указываем!
Ответ = Ждать ОбещаниеСО;

Если Ответ.КодСостояния = 200 Тогда
    
    // 2. Получаем тело ответа как ДвоичныеДанные
    ДвоичныеДанныеОтчета = Ответ.ПолучитьТелоКакДвоичныеДанные();
    
    Если ДвоичныеДанныеОтчета <> Неопределено Тогда
        ДокPDF = Новый ДокументPDF;
        
        // 3. Открываем поток для чтения из оперативной памяти
        ПотокЧтения = ДвоичныеДанныеОтчета.ОткрытьПотокДляЧтения();
        
        // 4. Читаем PDF из потока, а не с диска
        ДокPDF.Прочитать(ПотокЧтения);
        
        // 5. Показываем документ пользователю
        ДокPDF.Показать(); 
        
        // Удалять ничего не нужно, так как файл "C:/Temp/Отчет.pdf" не создавался.
    КонецЕсли;
    
Иначе
    Сообщение = Новый СообщениеПользователю;
    Сообщение.Текст = "Ошибка: " + Ответ.КодСостояния;
    Сообщение.Сообщить();
КонецЕсли;

Важные нюансы реализации

При использовании данного метода стоит учитывать несколько моментов:

  1. Расход оперативной памяти. Поскольку файл загружается в RAM полностью, при работе с очень большими PDF-файлами (сотни мегабайт) может возрасти потребление памяти клиентом 1С. Однако для типичных печатных форм (счета, накладные, отчеты до 10-20 Мб) это совершенно безопасно.
  2. Разделение этапов. Некоторые разработчики совершают ошибку: скачивают файл на диск (как в первом примере), затем считывают его в ДвоичныеДанные с диска, удаляют файл и передают данные в ДокументPDF.
    Это лишние действия. Сразу запрашивайте тело ответа как двоичные данные, минуя жесткий диск.
  3. Особенности асинхронности. В примере использован Ждать (новые возможности асинхронности). Если вы используете старые версии платформы или синхронные вызовы, логика работы с ДвоичныеДанные.ОткрытьПотокДляЧтения() остается точно такой же.

Альтернативный вариант: Временные файлы

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

Вместо того чтобы сохранять файл под фиксированным именем C:/Temp/Отчет.pdf, используйте системную функцию для генерации уникального имени.


// Получаем уникальное имя временного файла (например, tmp12345.pdf)
ПутьКФайлу = ПолучитьИмяВременногоФайла("pdf"); 

// Сохраняем туда ответ
Соединение.Получить(Запрос, ПутьКФайлу);

ДокPDF = Новый ДокументPDF;
ДокPDF.Прочитать(ПутьКФайлу);
ДокPDF.Показать();

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

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

Заключение

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

Используйте метод Ответ.ПолучитьТелоКакДвоичныеДанные() в связке с ОткрытьПотокДляЧтения(), и вам больше не придется бороться с удалением заблокированных файлов.

← На главную