Почему возникает ошибка совместного доступа при удалении файла после чтения текста в 1С?

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

Работа с временными файлами — одна из самых частых задач при интеграции с внешним оборудованием, например, сканерами штрихкодов, или при обмене данными. Однако нередко разработчики сталкиваются с ситуацией, когда платформа 1С выдает ошибку при попытке удалить файл, который только что был прочитан. Сообщение об ошибке обычно выглядит так: Ошибка совместного доступа к файлу... The process cannot access the file because it is being used by another process.

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

Анализ ситуации: поиск ошибки в коде

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


// ... Получение имени файла ...
ИмяФайлаШтрихкода = ПолучитьИмяВременногоФайла("txt");

// Внимательно смотрим на эти строки:
ЧтениеТекста = Новый ЧтениеТекста(); // Объект 1
Текст = Новый ЧтениеТекста(ИмяФайлаШтрихкода, КодировкаТекста.UTF8); // Объект 2 (захватывает файл!)

Штрихкод = СокрЛП(Текст.Прочитать()); // Читаем через Объект 2

ЧтениеТекста.Закрыть(); // Закрываем Объект 1 (который пустой и не связан с файлом)
ЧтениеТекста = Неопределено;
    
Попытка
    УдалитьФайлы(ИмяФайлаШтрихкода); // ОШИБКА ЗДЕСЬ
Исключение
    // Ошибка совместного доступа
КонецПопытки;

Проанализируем этот код построчно. Мы видим классическую ошибку именования переменных ("Copy-Paste error"), которую помог бы найти инструмент для автоматического редактирования модулей:

  1. Создается переменная ЧтениеТекста, инициализируемая пустым конструктором. Она не держит никакой файл.
  2. Создается переменная Текст, которая открывает реальный файл на диске. Именно этот объект устанавливает блокировку файловой системы (file handle).
  3. Чтение происходит через переменную Текст.
  4. Метод .Закрыть() вызывается у переменной ЧтениеТекста. То есть мы закрываем пустой объект, который ничего не держал.
  5. Переменная Текст остается активной, метод Закрыть() для нее не вызван. Следовательно, файл остается заблокированным процессом 1С (rphost или 1cv8).

Когда мы вызываем УдалитьФайлы, операционная система Windows сообщает нам, что файл все еще занят, так как объект, держащий его (Текст), еще жив в памяти.

Правильное закрытие потоков

Для исправления ситуации необходимо гарантировать, что мы закрываем именно тот объект, который открыл файл. Исправленный код должен выглядеть так:


// Правильный подход
ЧтениеТекста = Новый ЧтениеТекста(ИмяФайлаШтрихкода, КодировкаТекста.UTF8);
Попытка
    Штрихкод = СокрЛП(ЧтениеТекста.Прочитать());
Исключение
    // Обработка ошибки чтения
КонецПопытки;

// Явное закрытие потока
ЧтениеТекста.Закрыть();
// Обнуление переменной (помогает сборщику мусора, но .Закрыть() важнее)
ЧтениеТекста = Неопределено; 

// Теперь файл свободен и его можно удалять
УдалитьФайлы(ИмяФайлаШтрихкода);

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

Механизм работы с файловыми дескрипторами

Давайте углубимся в техническую часть. Почему вообще нужно вызывать метод Закрыть()? Разве 1С не может сама освободить файл?

В платформе 1С объекты для работы с файловой системой (такие как ЧтениеТекста, ЗаписьТекста, ЧтениеZipФайла) захватывают файловый ресурс (handle) на уровне операционной системы. Пока этот ресурс захвачен, другие процессы (включая саму операционную систему при попытке удаления) не могут модифицировать или удалять файл.

Существует два способа освобождения ресурса:

  1. Явный вызов метода Закрыть(): Это самый надежный способ. Он форсирует немедленный сброс буферов и освобождение дескриптора файла ОС, не дожидаясь уничтожения объекта 1С.
  2. Сборщик мусора (Garbage Collector): Если разработчик забывает вызвать метод .Закрыть(), файл остается заблокированным до тех пор, пока переменная не выйдет из области видимости и не отработает автоматический сборщик мусора. Этот момент не детерминирован (непредсказуем). Файл может оставаться занятым еще несколько секунд или минут после выполнения кода.

Именно поэтому вариант ЧтениеТекста = Неопределено без предварительного .Закрыть() является плохой практикой. Он лишь сообщает платформе, что переменная больше не нужна, но физическое закрытие файла произойдет "когда-нибудь потом".

Альтернатива: ТекстовыйДокумент

Если вы работаете с небольшими текстовыми файлами (например, простые логи или штрихкоды), можно использовать объект ТекстовыйДокумент вместо ЧтениеТекста. У этих объектов принципиально разная логика работы.

Объект ЧтениеТекста работает в поточном режиме: он держит файл открытым все время, пока вы читаете строки, что часто требуется при использовании модуля для конвертации JSON/XML/CSV.

Объект ТекстовыйДокумент при вызове метода Прочитать() считывает файл в оперативную память целиком и сразу же закрывает файл. После выполнения этой строчки файл гарантировано свободен на уровне файловой системы, даже если сам объект ТекстовыйДокумент еще существует в памяти.


// Пример использования ТекстовыйДокумент
ТекстДок = Новый ТекстовыйДокумент;
ТекстДок.Прочитать(ИмяФайлаШтрихкода, КодировкаТекста.UTF8); // Файл открылся, прочитался и закрылся
Штрихкод = СокрЛП(ТекстДок.ПолучитьТекст());

// Можно сразу удалять, не вызывая никаких .Закрыть()
УдалитьФайлы(ИмяФайлаШтрихкода);

Этот метод безопаснее с точки зрения блокировок, но потребляет больше оперативной памяти, так как грузит файл целиком.

Влияние внешних факторов: Антивирусы

Представим ситуацию: ваш код написан идеально. Вы открыли файл, прочитали, закрыли методом .Закрыть(), но на следующей строке УдалитьФайлы() все равно падает с ошибкой доступа. В чем дело?

Здесь в игру вступают внешние программы, чаще всего — антивирусы. Когда 1С (или любая другая программа) закрывает измененный файл, антивирусный драйвер перехватывает это событие и мгновенно блокирует файл для сканирования. Если ваша команда УдалитьФайлы() поступает через миллисекунды после закрытия файла, она натыкается на блокировку антивируса.

Для решения этой проблемы рекомендуется использовать алгоритм с повторными попытками. Мы не можем приказать антивирусу не проверять файл, но мы можем немного подождать.

Универсальный алгоритм безопасного удаления

Рассмотрим надежный паттерн для удаления временных файлов, который учитывает возможные задержки файловой системы и антивирусов:


Процедура БезопасноеУдалениеФайла(ПутьКФайлу)
    
    Сч = 0;
    Пока Сч < 3 Цикл // Делаем 3 попытки
        Попытка
            УдалитьФайлы(ПутьКФайлу);
            Прервать; // Если удалилось без ошибок - выходим из цикла
        Исключение
            // Файл занят, ждем немного
            Сч = Сч + 1;
            Если Сч = 3 Тогда
                // Если после 3 попыток не удалилось - пробрасываем ошибку или пишем в лог
                ЗаписьЖурналаРегистрации("УдалениеФайла", УровеньЖурналаРегистрации.Ошибка, , , 
                    "Не удалось удалить файл: " + ОписаниеОшибки());
                ВызватьИсключение;
            КонецЕсли;
            
            // Пауза 200 мс (для Windows можно использовать команду Ping или внешнюю компоненту)
            // В новых версиях платформы (8.3.18+) доступен асинхронный код или Ждать, 
            // но в синхронном коде на сервере часто используют такой трюк:
            ЗапуститьПриложение("ping -n 1 -w 200 127.0.0.1", , Истина);
        КонецПопытки;
    КонецЦикла;
    
КонецПроцедуры

Особенности работы с ЗапуститьПриложение

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


// Неправильно: 1С продолжит работу сразу, не дожидаясь завершения
ЗапуститьПриложение("Generator.exe " + ИмяФайла); 
Текст = Новый ЧтениеТекста(ИмяФайла); // Скорее всего упадет

// Правильно:
ЗапуститьПриложение("Generator.exe " + ИмяФайла, , Истина); // Ждем завершения процесса
Текст = Новый ЧтениеТекста(ИмяФайла); // Теперь безопасно

Резюме

Чтобы избежать ошибок при удалении файлов в 1С:

  1. Проверяйте переменные: Убедитесь, что вы вызываете .Закрыть() у той переменной, которая инициализирована файлом.
  2. Используйте явное закрытие: Не полагайтесь на сборщик мусора или присвоение Неопределено.
  3. Рассмотрите ТекстовыйДокумент: Для простых задач он управляет блокировками проще.
  4. Учитывайте инерцию системы: Антивирусы и индексация могут кратковременно блокировать файлы. Используйте цикл попыток при удалении.
← На главную