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

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

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

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

Причина возникновения проблемы

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

Особую сложность представляют системные файлы, такие как Thumbs.db. Это база данных эскизов, которую Windows создает автоматически. Если папка открыта у кого-то в проводнике, этот файл будет заблокирован практически постоянно, что вызовет ошибку при попытке очистить папку целиком через маску "*".

Решение 1. Программный цикл ожидания (Busy Loop)

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


Процедура ОчиститьКаталогСПовтором(ПутьКПапке)
    МаксимальноеКоличествоПопыток = 5;
    ИнтервалОжидания = 10; // секунд
    УдалосьУдалить = Ложь;
    НомерПопытки = 1;

    Пока НомерПопытки <= МаксимальноеКоличествоПопыток Цикл
        Попытка
            УдалитьФайлы(ПутьКПапке, "*");
            УдалосьУдалить = Истина;
            Прервать;
        Исключение
            ЗаписьЖурналаРегистрации("ФайловыеОперации", УровеньЖурналаРегистрации.Предупреждение, , , 
                "Попытка " + НомерПопытки + " не удалась: " + ОписаниеОшибки());
            
            // Реализуем задержку
            КонДата = ТекущаяДата() + ИнтервалОжидания;
            Пока ТекущаяДата() < КонДата Цикл
                // Пустой цикл ожидания
            КонецЦикла;
            
            НомерПопытки = НомерПопытки + 1;
        КонецПопытки;
    КонецЦикла;

    Если Не УдалосьУдалить Тогда
        ВызватьИсключение "Не удалось очистить каталог после " + МаксимальноеКоличествоПопыток + " попыток.";
    КонецЕсли;
КонецПроцедуры

Важное замечание: Метод "пустого цикла" Пока ТекущаяДата() < КонДата сильно нагружает процессор, так как 1С выполняет миллионы итераций в секунду, просто проверяя время. Это допустимо для коротких задержек в 1-2 секунды, но для длительного ожидания лучше использовать другие методы.

Решение 2. Использование системной паузы через WScript.Shell

Чтобы не перегружать процессор "пустым циклом", мы можем задействовать возможности операционной системы. В Windows нет прямой команды "sleep" в командной строке, но программисты часто используют команду ping или timeout для создания управляемой паузы.

Рассмотрим пример функции, которая делает паузу более элегантно:


Процедура Пауза(Секунды)
    СтрокаЗапроса = "ping -n 1 -w " + Формат(1000 * Секунды, "ЧДЦ=0; ЧГ=") + " 127.255.255.255";
    WshShell = Новый COMОбъект("WScript.Shell");
    // Параметр 0 - скрыть окно, -1 - ждать завершения
    WshShell.Run(СтрокаЗапроса, 0, -1);
КонецПроцедуры

Этот метод можно вставить в блок Исключение нашего основного алгоритма. Процесс 1С будет "засыпать", ожидая завершения работы внешней команды ping, что практически не потребляет ресурсы процессора.

Решение 3. Обработчик ожидания на клиенте

Если действие инициируется пользователем нажатием кнопки на форме, мы можем использовать встроенный механизм платформы — ПодключитьОбработчикОжидания. Это самый правильный подход с точки зрения UX (пользовательского интерфейса), так как интерфейс программы не будет "замерзать".

Проанализируем шаги реализации:

  1. Пользователь нажимает кнопку.
  2. Программа пытается выполнить удаление на сервере.
  3. Если возникла ошибка, мы сообщаем пользователю: "Файл занят, попробуем еще раз через 5 секунд", и подключаем обработчик ожидания.
  4. Через 5 секунд процедура вызывается снова.

&НаКлиенте
Перем ТекущаяПопытка;

&НаКлиенте
Процедура КомандаУдалить(Команда)
    ТекущаяПопытка = 1;
    ВыполнитьУдалениеСПовтором();
КонецПроцедуры

&НаКлиенте
Процедура ВыполнитьУдалениеСПовтором()
    Если ПопыткаУдаленияНаСервере() Тогда
        ПоказатьОповещениеПользователя("Успех", , "Файлы успешно удалены");
    ИначеЕсли ТекущаяПопытка < 5 Тогда
        ТекущаяПопытка = ТекущаяПопытка + 1;
        ПодключитьОбработчикОжидания("ВыполнитьУдалениеСПовтором", 5, Истина);
    Иначе
        ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Не удалось удалить файлы. Возможно, они открыты в другой программе.");
    КонецЕсли;
КонецПроцедуры

Решение 4. Архитектурный подход с использованием флаг-файлов

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

Алгоритм работы будет следующим:

  1. Процесс обмена перед началом записи создает в папке пустой файл exchange.lock.
  2. Метод УдалитьФайлы проверяет наличие этого файла. Если он существует — 1С даже не пытается начать удаление, а сразу уходит в ожидание.
  3. После завершения обмена процесс выгрузки удаляет exchange.lock.

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

Проблема Thumbs.db и поштучное удаление

Как было замечено в обсуждении, метод УдалитьФайлы(Путь, "*") часто спотыкается на системных файлах. Если ваша задача — очистить папку от пользовательских данных, проанализируем более гибкий подход: перебор файлов в цикле.


МассивФайлов = НайтиФайлы(ПутьКПапке, "*.*");
Для Каждого ТекущийФайл Из МассивФайлов Цикл
    // Пропускаем системные файлы, если нужно
    Если ТекущийФайл.Имя = "Thumbs.db" Тогда
        Продолжить;
    КонецЕсли;
    
    Попытка
        УдалитьФайлы(ТекущийФайл.ПолноеИмя);
    Исключение
        // Логируем ошибку для конкретного файла и идем дальше
    КонецПопытки;
КонецЦикла;

Использование НайтиФайлы позволяет нам более точно контролировать процесс. Мы можем проверять атрибуты (например, не удалять файлы с атрибутом ТолькоЧтение) или расширения, избегая попыток удаления того, что гарантированно заблокировано системой.

Резюме и рекомендации

Для решения проблемы совместного доступа мы рекомендуем комбинировать подходы в зависимости от контекста задачи:

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

← На главную