Невозможно удалить каталог с помощью Directory.Delete (путь, истина)


383

Я использую .NET 3.5, пытаюсь рекурсивно удалить каталог, используя:

Directory.Delete(myPath, true);

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

Тем не менее, я иногда получаю это:

System.IO.IOException: The directory is not empty.
    at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
    at System.IO.Directory.DeleteHelper(String fullPath, String userPath, Boolean recursive)
    at System.IO.Directory.Delete(String fullPath, String userPath, Boolean recursive)
    ...

Я не удивлен, что метод иногда выдает, но я удивлен, когда получаю это конкретное сообщение, когда рекурсивно верно. (Я знаю, что каталог не пуст.)

Есть ли причина, по которой я вижу это вместо AccessViolationException?


13
Вы не увидите AccessViolationException - это для недопустимых операций с указателями, а не для доступа к диску.
Джо Уайт

1
Похоже, что это какая-то проблема ввода-вывода, за исключением того, что каталог не пустой, например, дескрипторы открытых файлов или что-то еще. Я попытался бы использовать опцию рекурсивного удаления, затем в перехвате IOException, найти и закрыть все открытые дескрипторы файлов, а затем повторить попытку. Здесь обсуждается это: stackoverflow.com/questions/177146/…
Дэн Чарпстер

Ответы:


231

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


Я столкнулся с этой проблемой раньше.

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

Просто вставьте этот код в свой проект.

public static void DeleteDirectory(string target_dir)
{
    string[] files = Directory.GetFiles(target_dir);
    string[] dirs = Directory.GetDirectories(target_dir);

    foreach (string file in files)
    {
        File.SetAttributes(file, FileAttributes.Normal);
        File.Delete(file);
    }

    foreach (string dir in dirs)
    {
        DeleteDirectory(dir);
    }

    Directory.Delete(target_dir, false);
}

Кроме того, я лично добавляю ограничение на области машины, которые разрешено удалять, потому что вы хотите, чтобы кто-то вызывал эту функцию C:\WINDOWS (%WinDir%)или C:\.


117
Это нонсенс. Directory.Delete (myPath, true) - это перегрузка, которая удаляет все файлы, которые находятся в структуре каталогов. Если вы хотите ошибиться, ошибитесь с ответом Райана С.
сиг. Толлеранца

35
+1, потому что хотя Directory.Delete () действительно удаляет файлы внутри своих подкаталогов (с recursive = true), он выдает «IOException: Directory не пусто», если один из подкаталогов или файлов доступен только для чтения. Таким образом, это решение работает лучше, чем Directory.Delete ()
Энтони Брайен

17
Ваше утверждение, что Directory.Delete(path, true)не удаляет файлы, неверно. См. MSDN msdn.microsoft.com/en-us/library/fxeahc5f.aspx
Константин Спирин

20
-1 Может кто-нибудь, пожалуйста, поставит четкий маркер, что обоснованность такого подхода очень сомнительна. Если происходит Directory.Delete(string,bool)сбой, что-то блокируется или неправильно пропускается, и не существует единого размера, подходящего для решения этой проблемы. Людям нужно решать эту проблему в своем контексте, и мы не должны сильно растерять каждую идею о проблеме (с повторными попытками и глотанием исключений) и надеяться на хороший результат.
Рубен Бартелинк

37
Остерегайтесь этого подхода, если ваш каталог, который вы удаляете, имеет ярлыки / символические ссылки на другие папки - вы можете удалить больше, чем ожидали
Chanakya

182

Если вы пытаетесь рекурсивно удалить каталог, aи каталог a\bоткрыт в проводнике, bон будет удален, но вы получите сообщение об ошибке «каталог не пустой», aдаже если он пуст, когда вы идете и смотрите. Текущий каталог любого приложения (включая Проводник) сохраняет дескриптор каталога . Когда вы звоните Directory.Delete(true), он удаляет снизу вверх:, bзатем a. Если bон открыт в Explorer, Explorer обнаружит удаление b, поменяет каталог вверх cd ..и очистит открытые дескрипторы. Поскольку файловая система работает асинхронно, Directory.Deleteоперация завершается ошибкой из-за конфликтов с проводником.

Неполное решение

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

// incomplete!
try
{
    Directory.Delete(path, true);
}
catch (IOException)
{
    Thread.Sleep(0);
    Directory.Delete(path, true);
}

Но это работает, только если открытый каталог является непосредственным дочерним элементом удаляемого каталога. Если a\b\c\dон открыт в Проводнике, и вы используете его a, этот метод завершится ошибкой после удаления dи c.

Несколько лучшее решение

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

/// <summary>
/// Depth-first recursive delete, with handling for descendant 
/// directories open in Windows Explorer.
/// </summary>
public static void DeleteDirectory(string path)
{
    foreach (string directory in Directory.GetDirectories(path))
    {
        DeleteDirectory(directory);
    }

    try
    {
        Directory.Delete(path, true);
    }
    catch (IOException) 
    {
        Directory.Delete(path, true);
    }
    catch (UnauthorizedAccessException)
    {
        Directory.Delete(path, true);
    }
}

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

Возможно, вы сможете уменьшить количество исключений, генерируемых и перехваченных в типичных условиях, добавляя Thread.Sleep(0)в начале tryблока. Кроме того, существует риск того, что при большой загрузке системы вы можете выполнить обе Directory.Deleteпопытки и потерпеть неудачу. Считайте это решение отправной точкой для более надежного рекурсивного удаления.

Общий ответ

Это решение касается только особенностей взаимодействия с Windows Explorer. Если вам нужна надежная операция удаления, следует помнить, что все (антивирусный сканер и т. Д.) Может иметь открытый дескриптор того, что вы пытаетесь удалить, в любое время. Поэтому вы должны попробовать позже. Сколько позже и сколько раз вы попробуете, зависит от того, насколько важно удалить объект. Как указывает MSDN ,

Надежный файловый итерационный код должен учитывать многие сложности файловой системы.

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

( Изменить : много. Первоначально у этого ответа было только первое, неполное решение.)


11
Похоже, что он вызывает Directory.Delete (путь, истина), в то время как путь или одна из папок / файлов в пути открыта или выбрана в проводнике Windows, вызывает IOException. Закрытие Windows Explorer и повторный запуск моего существующего кода без предложенного выше метода try / catch работали нормально.
Дэвид Алперт

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

1
@CarlosLiu Потому что это дает «Исследователю шанс освободить дескриптор каталога»
Дмитрий Гончар

4
Происходит следующее: система просит проводника «освободить дескриптор каталога», а затем пытается удалить каталог. Если дескриптор каталога не был удален вовремя, возникает исключение, и выполняется catchблок (тем временем, Explorer все еще освобождает каталог, поскольку не было отправлено никакой команды, чтобы сказать ему не делать этого). Вызов Thread.Sleep(0)может быть или не быть необходимым, так как catchблок уже дал системе немного больше времени, но он обеспечивает некоторую дополнительную безопасность при низкой стоимости. После этого Deleteвызывается с уже выпущенным каталогом.
Захари Книбель

1
@PandaWood на самом деле только этот сон (100) работал для меня. Сон (0) не работал. Я понятия не имею, что происходит и как решить это правильно. Я имею в виду, что если это зависит от нагрузки на сервер и в будущем должно быть 300 или 400? Как узнать это. Должен быть другой правильный путь ...
Роман

43

Прежде чем идти дальше, проверьте следующие причины, которые находятся под вашим контролем:

  • Папка установлена ​​как текущая директория вашего процесса? Если да, сначала измените его на что-то другое.
  • Вы открыли файл (или загрузили DLL) из этой папки? (и забыл закрыть / разгрузить его)

В противном случае проверьте следующие законные причины вне вашего контроля:

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

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

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

  • поисковые индексы
  • антивирусы
  • программное обеспечение для резервного копирования

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

private static void DeleteRecursivelyWithMagicDust(string destinationDir) {
    const int magicDust = 10;
    for (var gnomes = 1; gnomes <= magicDust; gnomes++) {
        try {
            Directory.Delete(destinationDir, true);
        } catch (DirectoryNotFoundException) {
            return;  // good!
        } catch (IOException) { // System.IO.IOException: The directory is not empty
            System.Diagnostics.Debug.WriteLine("Gnomes prevent deletion of {0}! Applying magic dust, attempt #{1}.", destinationDir, gnomes);

            // see http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true for more magic
            Thread.Sleep(50);
            continue;
        }
        return;
    }
    // depending on your use case, consider throwing an exception here
}

На мой взгляд, такой помощник должен использоваться для всех удалений, потому что ложные сбои всегда возможны. Однако ВЫ ДОЛЖНЫ ПРИНЯТЬ ЭТОТ КОД К ВАШЕМУ ИСПОЛЬЗОВАНИЮ, а не просто слепо копировать его.

У меня были ложные сбои для внутренней папки данных, созданной моим приложением, расположенной в папке% LocalAppData%, поэтому мой анализ выглядит следующим образом:

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

  2. Там нет ценного пользовательского материала, поэтому нет риска принудительного удаления чего-либо по ошибке.

  3. Будучи внутренней папкой данных, я не ожидаю, что она будет открыта в проводнике, по крайней мере, я не чувствую необходимости конкретно обрабатывать дело (т.е. я прекрасно справляюсь с этим делом через службу поддержки).

  4. Если все попытки не удаются, я предпочитаю игнорировать ошибку. В худшем случае приложение не может распаковать некоторые новые ресурсы, вылетает и предлагает пользователю обратиться в службу поддержки, что является приемлемым для меня, если это происходит не часто. Или, если приложение не аварийно завершает работу, оно оставляет некоторые старые данные, что опять-таки приемлемо для меня.

  5. Я решил ограничить количество попыток до 500 мс (50 * 10). Это произвольный порог, который работает на практике; Я хотел, чтобы порог был достаточно коротким, чтобы пользователи не убивали приложение, думая, что оно перестало отвечать. С другой стороны, полсекунды - это достаточно времени, чтобы нарушитель мог закончить обработку моей папки. Судя по другим ответам SO, которые иногда даже Sleep(0)приемлемы, очень немногие пользователи когда-либо будут испытывать больше, чем одна повторная попытка.

  6. Я повторяю каждые 50 мс, что является другим произвольным числом. Я чувствую, что если файл обрабатывается (индексируется, проверяется), когда я пытаюсь удалить его, 50 мс - это подходящее время, чтобы ожидать завершения обработки в моем случае. Кроме того, 50 мс достаточно мал, чтобы не привести к заметному замедлению; опять же, Sleep(0)кажется, этого достаточно во многих случаях, поэтому мы не хотим слишком долго откладывать.

  7. Код повторяется при любых исключениях ввода-вывода. Обычно я не ожидаю каких-либо исключений при доступе к% LocalAppData%, поэтому я выбрал простоту и принял риск задержки в 500 мс в случае легитимного исключения. Я также не хотел придумывать способ обнаружить точное исключение, к которому я хочу повторить попытку.


7
PPS Несколько месяцев спустя я рад сообщить, что этот (несколько безумный) фрагмент кода полностью решил проблему. Запросы поддержки по этой проблеме сведены к нулю (примерно с 1-2 в неделю).
Андрей Таранцов

1
+0 Хотя это более надежно и менее «вот оно; идеальное решение для вас », чем stackoverflow.com/a/7518831/11635 , для меня то же самое - программирование по совпадению - обращайтесь с осторожностью. Один полезный пункт воплощено в коде, что если вы собираетесь сделать повторную попытку, вам нужно учитывать , что вы находитесь в гонке с неоднозначностью , имеет ли директория «Унесенных» с момента последней попытки [и niave Directory.Existsохранник не решить это.]
Рубен Бартелинк

1
люблю это ... не знаю, что я делаю, что это всегда больно для меня ... но это не потому, что у меня есть каталог, открытый в проводнике ... не так много шума в Интернете об этом больше или, конечно, ошибка ... по крайней мере, у меня и у Андрея есть способ с этим справиться :)
TCC

2
@RubenBartelink Хорошо, так что я думаю, что мы можем договориться об этом: опубликовать кусок кода, который работает для одного конкретного приложения (и никогда не предназначался для каждого случая), так как ответ SO будет плохой услугой для многих новичков и / или невежественные разработчики. Я дал это как отправную точку для настройки, но да, некоторые люди будут использовать это как есть, и это плохо.
Андрей Таранцов

2
@nopara Вам не нужно сравнение; если мы не в курсе, мы потерпели неудачу. И да, во многих случаях вы захотите выбросить исключение, а затем добавить соответствующий код обработки ошибок в стек, вероятно, с помощью сообщения, видимого пользователю.
Андрей Таранцов

18

Современный асинхронный ответ

Принятый ответ просто неверен, он может сработать для некоторых людей, потому что время, необходимое для получения файлов с диска, освобождает все, что блокировало файлы. Дело в том, что это происходит потому, что файлы блокируются каким-либо другим процессом / потоком / действием. Другие ответы используют Thread.Sleep(Yuck), чтобы через некоторое время повторить попытку удаления каталога. Этот вопрос нуждается в повторении с более современным ответом.

public static async Task<bool> TryDeleteDirectory(
   string directoryPath,
   int maxRetries = 10,
   int millisecondsDelay = 30)
{
    if (directoryPath == null)
        throw new ArgumentNullException(directoryPath);
    if (maxRetries < 1)
        throw new ArgumentOutOfRangeException(nameof(maxRetries));
    if (millisecondsDelay < 1)
        throw new ArgumentOutOfRangeException(nameof(millisecondsDelay));

    for (int i = 0; i < maxRetries; ++i)
    {
        try
        {
            if (Directory.Exists(directoryPath))
            {
                Directory.Delete(directoryPath, true);
            }

            return true;
        }
        catch (IOException)
        {
            await Task.Delay(millisecondsDelay);
        }
        catch (UnauthorizedAccessException)
        {
            await Task.Delay(millisecondsDelay);
        }
    }

    return false;
}

Модульные тесты

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

[Fact]
public async Task TryDeleteDirectory_FileLocked_DirectoryNotDeletedReturnsFalse()
{
    var directoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
    var subDirectoryPath = Path.Combine(Path.GetTempPath(), "SubDirectory");
    var filePath = Path.Combine(directoryPath, "File.txt");

    try
    {
        Directory.CreateDirectory(directoryPath);
        Directory.CreateDirectory(subDirectoryPath);

        using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write))
        {
            var result = await TryDeleteDirectory(directoryPath, 3, 30);
            Assert.False(result);
            Assert.True(Directory.Exists(directoryPath));
        }
    }
    finally
    {
        if (Directory.Exists(directoryPath))
        {
            Directory.Delete(directoryPath, true);
        }
    }
}

[Fact]
public async Task TryDeleteDirectory_FileLockedThenReleased_DirectoryDeletedReturnsTrue()
{
    var directoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
    var subDirectoryPath = Path.Combine(Path.GetTempPath(), "SubDirectory");
    var filePath = Path.Combine(directoryPath, "File.txt");

    try
    {
        Directory.CreateDirectory(directoryPath);
        Directory.CreateDirectory(subDirectoryPath);

        Task<bool> task;
        using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write))
        {
            task = TryDeleteDirectory(directoryPath, 3, 30);
            await Task.Delay(30);
            Assert.True(Directory.Exists(directoryPath));
        }

        var result = await task;
        Assert.True(result);
        Assert.False(Directory.Exists(directoryPath));
    }
    finally
    {
        if (Directory.Exists(directoryPath))
        {
            Directory.Delete(directoryPath, true);
        }
    }
}

Можете ли вы рассказать о том, что вы подразумеваете под «современным»? Каковы преимущества вашего подхода? Почему другие, по вашему мнению, не правы?
TinyRacoon

1
Другие не ошибаются. Они просто используют более старые API, Thread.Sleepкоторые вы должны избегать сегодня, и вместо этого используйте async/ awaitс Task.Delay. Это понятно, это очень старый вопрос.
Мухаммед Рехан Саид

Этот подход не будет работать в VB.Net (по крайней мере, с очень буквальным построчным преобразованием) из-заBC36943 'Await' cannot be used inside a 'Catch' statement, a 'Finally' statement, or a 'SyncLock' statement.
amonroejj

@amonroejj Вы должны использовать более старую версию. Это было исправлено.
Мухаммед Рехан Саид

Небольшое улучшение вместо возврата true, if (!Directory.Exists(directoryPath)) { return true; } await Task.Delay(millisecondsDelay); чтобы ждать, пока каталог действительно исчезнет
fuchs777

16

Одна важная вещь, о которой следует упомянуть (я добавил ее в качестве комментария, но мне не разрешено), это то, что поведение перегрузки изменилось с .NET 3.5 на .NET 4.0.

Directory.Delete(myPath, true);

Начиная с .NET 4.0 он удаляет файлы в самой папке, но НЕ в 3.5. Это можно увидеть и в документации MSDN.

.NET 4.0

Удаляет указанный каталог и, если указано, любые подкаталоги и файлы в каталоге.

.NET 3.5

Удаляет пустой каталог и, если указано, любые подкаталоги и файлы в каталоге.


3
Я думаю, что это только изменение документации ... если он удаляет только "пустой каталог", что будет означать удаление также файлов в каталоге с параметром 2 °? Если она пуста нет файлов ...
Pisu

Боюсь, ты ошибаешься. Я опубликовал это после тестирования кода с обеими версиями фреймворка. Удаление непустой папки в 3.5 вызовет исключение.
jettatore

15

У меня была та же проблема под Delphi. И в результате мое приложение блокировало каталог, который я хотел удалить. Каким-то образом каталог был заблокирован, когда я писал в него (некоторые временные файлы).

Уловка 22 состояла в том, что я сделал простую замену каталога на его родительский, прежде чем удалить его.


6
+1 Теперь есть кое-что, что msdn для Directory.Delete упоминает!
Рубен Бартелинк

3
какое-нибудь окончательное решение с полным примером исходного кода работает над этим?
Kiquenet

11

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

Process.Start("cmd.exe", "/c " + @"rmdir /s/q C:\Test\TestDirectoryContainingReadOnlyFiles"); 

(Измените немного, чтобы не запускать окно cmd на мгновение, которое доступно по всему интернету)


Приятно поделиться с нами, но не могли бы вы добавить немного изменений, необходимых для предотвращения запуска окна cmd, вместо того, чтобы предлагать нам искать его по сети?
ThunderGr

Это не работает В той же ситуации, когда я могу удалить файл из командной строки или Проводника, использование этого кода для вызова rmdir дает код завершения 145, который переводится как «Каталог не пуст». Он оставляет каталог пустым, но все еще на месте, точно так же, как Directory.Delete ("", true)
Кевин Куломб

@Kevin Coulombe, Хм ... Вы уверены, что используете переключатели / s / q?
Пиюш Сони

1
@KevinCoulombe: Да, это должны быть те компоненты COM. Когда я пробую старый добрый C #, он работает и удаляет каталог вместе с файлами внутри (только для чтения или не только для чтения).
Пиюш Сони

5
Если вы начинаете полагаться на внешние компоненты для того, что должно быть в фреймворке, то это «не идеальная» идея, потому что она больше не переносима (или более трудна). Что делать, если exe там нет? Или параметр / изменился? Если решение от Джереми Эдвардса работает, то следует
отдать

11

Вы можете воспроизвести ошибку, запустив:

Directory.CreateDirectory(@"C:\Temp\a\b\c\");
Process.Start(@"C:\Temp\a\b\c\");
Thread.Sleep(1000);
Directory.Delete(@"C:\Temp\a\b\c");
Directory.Delete(@"C:\Temp\a\b");
Directory.Delete(@"C:\Temp\a");

При попытке удалить каталог 'b' выдает IOException "Каталог не пустой". Это глупо, так как мы только что удалили каталог 'c'.

Насколько я понимаю, объяснение состоит в том, что каталог «c» помечается как удаленный. Но удаление еще не зафиксировано в системе. Система ответила, что работа выполнена, хотя на самом деле она все еще обрабатывается. Система, вероятно, ожидает, что файловый менеджер сосредоточится на родительском каталоге, чтобы зафиксировать удаление.

Если вы посмотрите на исходный код функции Delete ( http://referencesource.microsoft.com/#mscorlib/system/io/directory.cs ), то увидите, что она использует встроенную функцию Win32Native.RemoveDirectory. Это поведение "не ждать" отмечено здесь:

Функция RemoveDirectory помечает каталог для удаления при закрытии. Следовательно, каталог не удаляется до тех пор, пока не будет закрыт последний дескриптор каталога.

( http://msdn.microsoft.com/en-us/library/windows/desktop/aa365488(v=vs.85).aspx )

Сон и повторите это решение. См. Решение Ряскля.


8

У меня были странные проблемы с разрешением на удаление каталогов профилей пользователей (в C: \ Documents and Settings), несмотря на то, что я мог это делать в оболочке Explorer.

File.SetAttributes(target_dir, FileAttributes.Normal);
Directory.Delete(target_dir, false);

Мне не имеет смысла, что «файловая» операция делает с каталогом, но я знаю, что она работает, и этого мне достаточно!


2
Все еще нет надежды, когда в каталоге много файлов, и Explorer открывает папку, содержащую эти файлы.
видит

3

Этот ответ основан на: https://stackoverflow.com/a/1703799/184528 . Разница с моим кодом заключается в том, что мы возвращаем много удаленных подкаталогов и файлов только при необходимости, вызов Directory.Delete завершается неудачно с первой попытки (что может произойти из-за того, что проводник Windows просматривает каталог).

    public static void DeleteDirectory(string dir, bool secondAttempt = false)
    {
        // If this is a second try, we are going to manually 
        // delete the files and sub-directories. 
        if (secondAttempt)
        {
            // Interrupt the current thread to allow Explorer time to release a directory handle
            Thread.Sleep(0);

            // Delete any files in the directory 
            foreach (var f in Directory.GetFiles(dir, "*.*", SearchOption.TopDirectoryOnly))
                File.Delete(f);

            // Try manually recursing and deleting sub-directories 
            foreach (var d in Directory.GetDirectories(dir))
                DeleteDirectory(d);

            // Now we try to delete the current directory
            Directory.Delete(dir, false);
            return;
        }

        try
        {
            // First attempt: use the standard MSDN approach.
            // This will throw an exception a directory is open in explorer
            Directory.Delete(dir, true);
        }
        catch (IOException)
        {
            // Try again to delete the directory manually recursing. 
            DeleteDirectory(dir, true);
        }
        catch (UnauthorizedAccessException)
        {
            // Try again to delete the directory manually recursing. 
            DeleteDirectory(dir, true);
        } 
    }

Так как же удалить папку, если она была UnauthorizedAccessException? Это было бы просто бросить, снова. И снова. И снова ... Потому что каждый раз он собирается идти catchи снова вызывать функцию. А Thread.Sleep(0);не меняет ваши разрешения. В этот момент он должен просто зарегистрировать ошибку и завершиться неудачей. И этот цикл будет продолжаться до тех пор, пока открыт (под) каталог - он не закрывает его программно. Готовы ли мы просто позволить этому делать это до тех пор, пока эти вещи остаются открытыми? Есть ли способ лучше?
vapcguy

Если есть, UnauthorizedAccessExceptionон попытается вручную удалить каждый файл. Таким образом, он продолжает добиваться прогресса, обращаясь к структуре каталогов. Да, потенциально каждый файл и каталог будут выдавать одно и то же исключение, но это также может произойти просто потому, что проводник держит дескриптор к нему (см. Stackoverflow.com/a/1703799/184528 ). Я изменю «tryAgain» на «secondTry». чтобы было понятнее.
cdiggins

Чтобы ответить более кратко, он передает «true» и выполняет другой путь кода.
cdiggins

Правильно, видел ваши изменения, но моя точка зрения не в удалении файлов, а в удалении каталога. Я написал некоторый код, который я мог бы сделать по существу Process.Kill()для любого процесса, которым файл может быть заблокирован, и удалить файлы. Проблема, с которой я сталкиваюсь, заключается в удалении каталога, в котором один из этих файлов все еще был открыт (см. Stackoverflow.com/questions/41841590/… ). Итак, возвращаясь к этому циклу, независимо от того, что еще он делает, если он делает Directory.Delete()это снова в этой папке, он все равно не будет выполнен, если этот дескриптор не может быть освобожден.
vapcguy

И то же самое произойдет, UnauthorizedAccessExceptionпоскольку удаление файлов (при условии, что это даже разрешено, потому что получить этот код не удалось Directory.Delete()) волшебным образом не дает вам разрешения на удаление каталога.
vapcguy

3

Ни одно из перечисленных выше решений не помогло мне. В итоге я использовал отредактированную версию решения @ryascl, как показано ниже:

    /// <summary>
    /// Depth-first recursive delete, with handling for descendant 
    /// directories open in Windows Explorer.
    /// </summary>
    public static void DeleteDirectory(string path)
    {
        foreach (string directory in Directory.GetDirectories(path))
        {
            Thread.Sleep(1);
            DeleteDir(directory);
        }
        DeleteDir(path);
    }

    private static void DeleteDir(string dir)
    {
        try
        {
            Thread.Sleep(1);
            Directory.Delete(dir, true);
        }
        catch (IOException)
        {
            DeleteDir(dir);
        }
        catch (UnauthorizedAccessException)
        {
            DeleteDir(dir);
        }
    }

2

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

Последовательность будет:

Процесс удаления А:

  1. Очистить каталог
  2. Удалить (теперь пустой) каталог.

Если кто-то еще добавит файл между 1 и 2, тогда, возможно, 2 выдаст исключение из списка?


2

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

 public static void DeleteDirectory(string target_dir)
    {
        DeleteDirectoryFiles(target_dir);
        while (Directory.Exists(target_dir))
        {
            lock (_lock)
            {
                DeleteDirectoryDirs(target_dir);
            }
        }
    }

    private static void DeleteDirectoryDirs(string target_dir)
    {
        System.Threading.Thread.Sleep(100);

        if (Directory.Exists(target_dir))
        {

            string[] dirs = Directory.GetDirectories(target_dir);

            if (dirs.Length == 0)
                Directory.Delete(target_dir, false);
            else
                foreach (string dir in dirs)
                    DeleteDirectoryDirs(dir);
        }
    }

    private static void DeleteDirectoryFiles(string target_dir)
    {
        string[] files = Directory.GetFiles(target_dir);
        string[] dirs = Directory.GetDirectories(target_dir);

        foreach (string file in files)
        {
            File.SetAttributes(file, FileAttributes.Normal);
            File.Delete(file);
        }

        foreach (string dir in dirs)
        {
            DeleteDirectoryFiles(dir);
        }
    }

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


8
-1 О чем задержка? Нет программирования по совпадению, пожалуйста!
Рубен Бартелинк

1
@Ruben Я не говорил, что ты не прав насчет этого. Я только что сказал, что понизить голосование только за это - суровое наказание. Я согласен с вами, однако, 4 отрицательных голоса не привели к 4 отрицательным результатам. Я также приветствовал бы ваш комментарий, но не стал бы отказываться от ответа из-за необъяснимой задержки :)
ThunderGr

1
@RubenBartelink и другие: хотя мне не нравится этот код (я опубликовал другое решение с аналогичным подходом), задержка здесь является разумной. Проблема, скорее всего, находится вне контроля приложения; возможно, другое приложение периодически сканирует FS, таким образом блокируя папку на короткое время. Задержка решает проблему, сводя счет ошибок к нулю. Кого волнует, если мы не имеем ни малейшего представления о первопричине?
Андрей Таранцов

1
@RubenBartelink На самом деле, когда вы думаете об этом, не использовать подход с задержкой и повтором во время удаления каталога NTFS является безответственным решением здесь. Любой вид текущего обхода файла блокирует удаление, поэтому рано или поздно он обязательно завершится неудачей. И вы не можете ожидать, что все сторонние инструменты поиска, резервного копирования, антивируса и управления файлами останутся в вашей папке.
Андрей Таранцов

1
@RubenBartelink Другой пример, скажем, вы даете задержку в 100 мс, а самое высокое время блокировки любого программного обеспечения на целевом ПК - это программное обеспечение AV = 90 мс. Скажем, у него также есть программное обеспечение для резервного копирования, которое блокирует файлы на 70 мс. Теперь AV блокирует файл, ваше приложение ожидает 100 мс, что обычно нормально, но затем встречается с другой блокировкой, потому что программное обеспечение для резервного копирования начинает захват файла с отметки 70 мс AV-сканирования, и поэтому для освобождения файла потребуется еще 40 мс. Таким образом, хотя программное обеспечение AV занимает больше времени, а ваши 100 мс обычно дольше, чем любое из двух приложений, вы все равно должны учитывать, когда оно начинается в середине.
vapcguy

2

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

public class IOUtils
{
    public static void DeleteDirectory(string directory)
    {
        Directory.GetFiles(directory, "*", SearchOption.AllDirectories).ForEach(File.Delete);
        Directory.Delete(directory, true);
    }
}

Я сталкивался с случаями, когда это помогало, но, как правило, Directory.Delete удаляет файлы внутри каталогов после рекурсивного удаления, как описано в msdn .

Время от времени я сталкиваюсь с этим нерегулярным поведением также как пользователь Windows Explorer: иногда я не могу удалить папку (она думает, что бессмысленное сообщение «отказано в доступе»), но когда я развертываю и удаляю нижние элементы, я могу удалить верхние предметы также. Поэтому я думаю, что приведенный выше код имеет дело с аномалией ОС, а не с проблемой библиотеки базовых классов.


1

Каталог или файл в нем заблокированы и не могут быть удалены. Найдите виновника, который его запирает, и посмотрите, сможете ли вы его устранить.


T1000 to user-with-folder-open: «Вы уничтожены!»
vapcguy

1

Похоже, что выбор пути или подпапки в проводнике Windows достаточен, чтобы заблокировать одиночное выполнение Directory.Delete (path, true), выбрасывая IOException, как описано выше, и умирая вместо загрузки Windows Explorer из родительской папки и действуя как ожидается.


Похоже, это была моя проблема. Как только я закрыл Проводник и снова запустился, не исключение. Даже выбора родителя родителей было недостаточно. Я должен был на самом деле закрыть Explorer.
Скотт Марлоу

Да, это происходит и является причиной. Итак, есть идеи, как программно с этим справиться, или ответ всегда заключается в том, чтобы все 1000 пользователей закрыли эту папку?
vapcguy

1

У меня была эта проблема сегодня. Это происходило потому, что у меня был проводник Windows, открытый для каталога, который пытался удалить, вызывая рекурсивный вызов сбой и, следовательно, IOException. Убедитесь, что в каталоге нет ручек.

Кроме того, в MSDN ясно, что вам не нужно писать собственный отзыв: http://msdn.microsoft.com/en-us/library/fxeahc5f.aspx


1

У меня была такая же проблема с Windows Workflow Foundation на сервере сборки с TFS2012. Внутренне рабочий процесс называется Directory.Delete () с рекурсивным флагом, установленным в true. В нашем случае это связано с сетью.

Мы удаляли двоичную папку для отбрасывания на общем сетевом ресурсе, а затем заново создавали и заново заполняли ее последними двоичными файлами. Любая другая сборка потерпит неудачу. При открытии папки удаления после неудачной сборки папка была пустой, что указывает на то, что все аспекты вызова Directory.Delete () были успешными, за исключением удаления действительного каталога.

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

Два возможных решения в нашем случае:

  • Создайте рекурсивное удаление в нашем собственном коде с задержками и проверками между каждым шагом
  • Повторите попытку до X раз после IOException, давая задержку перед повторной попыткой

Последний метод быстрый и грязный, но, кажется, делает свое дело.


1

Это из-за FileChangesNotifications.

Это происходит начиная с ASP.NET 2.0. Когда вы удаляете какую-либо папку в приложении, она перезапускается . Вы можете увидеть это сами, используя ASP.NET Health Monitoring .

Просто добавьте этот код в ваш web.config / configuration / system.web:

<healthMonitoring enabled="true">
  <rules>
    <add name="MyAppLogEvents" eventName="Application Lifetime Events" provider="EventLogProvider" profile="Critical"/>
  </rules>
</healthMonitoring>


После этого проверьте Windows Log -> Application. Что здесь происходит:

При удалении папки, если есть Delete(path, true)подпапка, сначала удаляется подпапка. FileChangesMonitor достаточно знать об удалении и закрыть ваше приложение. Между тем ваш основной каталог еще не удален. Это событие из журнала:


введите описание изображения здесь


Delete() не закончил свою работу, и, поскольку приложение закрывается, возникает исключение:

введите описание изображения здесь

Если у вас нет подпапок в папке, которую вы удаляете, Delete () просто удаляет все файлы и эту папку, приложение тоже перезапускается, но вы не получаете никаких исключений , потому что перезапуск приложения ничего не прерывает. Но все равно вы теряете все внутрипроцессные сеансы, приложение не отвечает на запросы при перезапуске и т. Д.

Что теперь?

Существуют некоторые обходные пути и твики для отключения этого поведения, « Соединение каталогов» , « Отключение FCN с реестром» , « Остановка FileChangesMonitor с помощью Reflection» (поскольку нет открытого метода) , но все они, похоже, не правы, поскольку FCN существует для причина. Он следит за структурой вашего приложения , которая не является структурой ваших данных . Краткий ответ: разместите папки, которые вы хотите удалить за пределами вашего приложения. FileChangesMonitor не будет получать уведомлений, и ваше приложение не будет перезапускаться каждый раз. Вы не получите никаких исключений. Чтобы сделать их видимыми из Интернета, есть два способа:

  1. Создайте контроллер, который обрабатывает входящие вызовы, а затем отправляет файлы обратно, читая из папки вне приложения (вне wwwroot).

  2. Если ваш проект большой и производительность важнее всего, создайте отдельный маленький и быстрый веб-сервер для обслуживания статического контента. Таким образом вы оставите IIS свою конкретную работу. Это может быть на той же машине (mongoose для Windows) или на другой машине (nginx для Linux). Хорошей новостью является то, что вам не нужно платить дополнительную лицензию Microsoft для установки статического сервера контента в Linux.

Надеюсь это поможет.


1

Как упомянуто выше, «принятое» решение терпит неудачу в точках повторного анализа - но люди все еще отмечают его (???) Существует гораздо более короткое решение, которое правильно копирует функциональность:

public static void rmdir(string target, bool recursive)
{
    string tfilename = Path.GetDirectoryName(target) +
        (target.Contains(Path.DirectorySeparatorChar.ToString()) ? Path.DirectorySeparatorChar.ToString() : string.Empty) +
        Path.GetRandomFileName();
    Directory.Move(target, tfilename);
    Directory.Delete(tfilename, recursive);
}

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

Вы можете безопасно продолжить обработку, потому что старый каталог будет в стороне … даже если он не исчезнет, ​​потому что «файловая система все еще догоняет» (или любое другое оправдание, которое MS дал для предоставления сломанной функции) .

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

    ThreadPool.QueueUserWorkItem((o) => { Directory.Delete(tfilename, recursive); });

Вы все еще можете продолжать работать.


2
Можно ли упростить ваше назначение: string tfilename = Path.Combine (Path.GetDirectoryName (target), Path.GetRandomFileName ());
Пит

1
Я должен согласиться с Питом. Код, как написано, не добавит разделитель. Он взял мой путь \\server\C$\dirи сделал это \\server\C$asf.yuw. В результате я получил сообщение об ошибке Directory.Move()- Source and destination path must have identical roots. Move will not work across volumes. Сработало нормально, когда я использовал код Пита, КРОМЕ которого не обрабатывает ни заблокированные файлы, ни открытые каталоги, поэтому он никогда не попадает в ThreadPoolкоманду.
vapcguy

ВНИМАНИЕ: Этот ответ должен использоваться только с рекурсивным = true. Если false, это переместит каталог, даже если он не пуст. Что было бы ошибкой; Правильное поведение в этом случае - создать исключение и оставить каталог как есть.
ToolmakerSteve

1

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

В таких случаях вам нужно удалить \\\\?\C:\mydirвместо C:\mydir. О пределе в 260 символов вы можете прочитать здесь .


1

Я решил с этой техникой тысячелетия (вы можете оставить Thread.Sleep самостоятельно в улове)

bool deleted = false;
        do
        {
            try
            {
                Directory.Delete(rutaFinal, true);                    
                deleted = true;
            }
            catch (Exception e)
            {
                string mensaje = e.Message;
                if( mensaje == "The directory is not empty.")
                Thread.Sleep(50);
            }
        } while (deleted == false);

0

Если текущий каталог вашего приложения (или любого другого приложения) является тем, который вы пытаетесь удалить, это не будет ошибкой нарушения прав доступа, но каталог не будет пустым. Убедитесь, что это не ваше собственное приложение, изменив текущий каталог; также убедитесь, что каталог не открыт в какой-либо другой программе (например, Word, Excel, Total Commander и т. д.). Большинство программ перейдут в каталог последнего открытого файла, что может привести к этому.


0

в случае сетевых файлов Directory.DeleteHelper (recursive: = true) может вызвать IOException, что вызвано задержкой удаления файла


0

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


0

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


0

Я решил один возможный случай указанной проблемы, когда методы были асинхронными и закодированы следующим образом:

// delete any existing update content folder for this update
if (await fileHelper.DirectoryExistsAsync(currentUpdateFolderPath))
       await fileHelper.DeleteDirectoryAsync(currentUpdateFolderPath);

С этим:

bool exists = false;                
if (await fileHelper.DirectoryExistsAsync(currentUpdateFolderPath))
    exists = true;

// delete any existing update content folder for this update
if (exists)
    await fileHelper.DeleteDirectoryAsync(currentUpdateFolderPath);

Вывод? Существует некоторый асинхронный аспект избавления от дескриптора, используемого для проверки существования, с которым Microsoft не смогла поговорить. Это как если бы асинхронный метод внутри оператора if имел оператор if, действующий как оператор using.


0

Вам не нужно создавать дополнительный метод для рекурсивности или удалять файлы внутри папки extra. Все это происходит автоматически по телефону

DirectoryInfo.Delete ();

Подробности здесь .

Нечто подобное работает довольно хорошо:

  var directoryInfo = new DirectoryInfo("My directory path");
    // Delete all files from app data directory.

    foreach (var subDirectory in directoryInfo.GetDirectories())
    {
          subDirectory.Delete(true);// true set recursive paramter, when it is true delete sub file and sub folder with files too
    }

Передав true в качестве переменной для удаления метода, удаляются также вложенные файлы и вложенные папки с файлами.


-2

Ни один из приведенных выше ответов не работал для меня. Похоже, что использование моего собственного приложения DirectoryInfoв целевом каталоге приводило к тому, что оно оставалось заблокированным.

Принудительный сборщик мусора решил проблему, но не сразу. Несколько попыток удалить при необходимости.

Обратите внимание, Directory.Existsкак оно может исчезнуть после исключения. Я не знаю, почему удаление для меня было отложено (Windows 7 SP1)

        for (int attempts = 0; attempts < 10; attempts++)
        {
            try
            {
                if (Directory.Exists(folder))
                {
                    Directory.Delete(folder, true);
                }
                return;
            }
            catch (IOException e)
            {
                GC.Collect();
                Thread.Sleep(1000);
            }
        }

        throw new Exception("Failed to remove folder.");

1
-1 Программирование по стечению обстоятельств. Что делает объект, когда GC'd? Это хороший совет? (Я верю вам, когда вы говорите, что у вас есть проблема и что вы использовали этот код, и что вы чувствуете, что у вас нет проблемы сейчас, но это не
главное

@RubenBartelink Я согласен. Это хак. Код вуду, который делает что-то, когда неясно, что он решает или как. Я хотел бы найти правильное решение.
Reactgular

1
Моя проблема в том, что все, что он добавляет сверх stackoverflow.com/a/14933880/11635 , весьма спекулятивно. Если бы я мог, я бы дал -1 для дублирования и -1 для спекуляции / программирования по совпадению. Опрыскивание GC.Collect- это а) просто плохой совет и б) не достаточно распространенная общая причина того, что заблокированные каталоги заслуживают включения сюда. Просто выберите один из других и не сеите больше путаницы в умах невинных читателей
Рубен Бартелинк

3
Use GC.WaitForPendingFinalizers (); после GC.Collect (); это будет работать как ожидалось.
Хайнер,

Не уверен, непроверенные, но , возможно , лучше было бы сделать что - то с usingутверждением, то: using (DirectoryInfo di = new DirectoryInfo(@"c:\MyDir")) { for (int attempts = 0; attempts < 10; attempts++) { try { if (di.Exists(folder)) { Directory.Delete(folder, true); } return; } catch (IOException e) { Thread.Sleep(1000); } } }
vapcguy

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.