Неможливо видалити каталог із 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 / ...
Dan Csharpster

Відповіді:


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 () видаляє файли всередині своїх підкаталогів (з рекурсивними = істинними), він видає "IOException: Каталог не порожній", якщо один із підкаталогів або файлів є лише для читання. Тож це рішення працює краще, ніж 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хоча він порожній, коли ви переходите і дивитесь. Поточний каталог будь-якої програми (включаючи Explorer) зберігає обробку до каталогу . Коли ви телефонуєте Directory.Delete(true), він видаляється знизу вгору:, bтоді a. Якщо bв Провіднику відкрито, Explorer виявить видалення b, змінить каталог вгору cd ..і очистить відкриті ручки. Оскільки файлова система працює асинхронно, Directory.Deleteоперація не працює через конфлікти з Провідником.

Неповне рішення

Я спочатку розмістив таке рішення, з ідеєю перервати поточний потік, щоб дати можливість Explorer пропустити ручку каталогу.

// 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 та повторне відновлення мого існуючого коду без запропонованого вище спробу / лову спрацювало чудово.
Девід Алперт

1
Я не можу зрозуміти, як і чому це працює, але це працювало для мене під час встановлення атрибутів файлів і написання моєї власної рекурсивної функції не зробило.
Stilgar

1
@CarlosLiu Тому що це дає "Explorer можливість випустити ручку каталогу"
Дмитро Гончар

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). Це довільний поріг, який працює на практиці; Я хотів, щоб поріг був досить коротким, щоб користувачі не вбивали додаток, думаючи, що він перестав реагувати. З іншого боку, півсекунди - це достатньо часу, щоб порушник закінчив обробляти мою папку. Судячи з інших відповідей ТА, які іноді виявляються навіть Sleep(0)прийнятними, дуже мало користувачів коли-небудь відчують більше, ніж одну спробу.

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

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


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

1
+0 Хоча це і є більш надійним і менш "ось він; ідеальне рішення для вас, ніж stackoverflow.com/a/7518831/11635 , для мене те саме стосується - програмування за збігом обставин - поводиться обережно. Одним корисним моментом, втіленим у вашому коді, є те, що якщо ви збираєтеся зробити повторний спробу, вам потрібно врахувати, що ви перебуваєте в гонці з неоднозначністю, чи "Каталог" пройшов "з моменту останньої спроби [і Directory.Existsохоронець ніав би не
вирішуйте

1
люблю це ... не знаю, що я роблю, що для мене це завжди є больовою точкою ... але це не тому, що в мене відкритий каталог в провіднику ... не дуже багато хвилювань в Інтернеті з цього приводу -і менші помилки ... принаймні у мене з Андрієм є спосіб вирішити це.
TCC,

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

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 Ви повинні використовувати старішу версію. Це було виправлено.
Мухаммед Рехан Саїд

Невелике поліпшення замість повернення істинного, 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
будь-яке остаточне рішення з повним зразком вихідного коду, що працює над цим?
Кікенет

11

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

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

(Змініть трохи, щоб миттєво не запускати вікно cmd, яке доступне по всьому Інтернету)


Приємно поділитися з нами, але ви були б настільки добрими, щоб включити зміни, необхідні для запобігання запуску cmd вікна, замість того, щоб спонукати нас шукати його через мережу?
Грім

Це не працює. У тій же ситуації, коли я можу видалити файл із командного рядка або Провідника, за допомогою цього коду для виклику rmdir видає вихідний код 145, що перекладається на "Каталог не порожній". Він залишає каталог порожнім, але все ще на місці, точно так само, як Directory.Delete ("", true)
Кевін Куломбе

@Kevin Coulombe, Humm ... Ви впевнені, що використовуєте перемикачі / 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' закреслено як видалений. Але видалення ще не зафіксовано в системі. Система має відповідь, завдання виконано, а насправді вона все ще обробляється. Система, ймовірно, зачекає, коли програма проводника зосередиться на батьківському каталозі, щоб здійснити видалення.

Якщо ви подивитесь на вихідний код функції Видалити ( 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 )

Сон і повтори - це рішення. Cf розчин Rascl.


8

У мене виникли ті дивні проблеми з дозволом при видаленні каталогів профілю користувача (в C: \ Documents and Settings), незважаючи на те, що можна було це зробити в оболонці Провідника.

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

Немає сенсу для мене, що операція з "файлом" працює в каталозі, але я знаю, що вона працює і цього мені достатньо!


2
Досі не сподіваюся, що в каталозі багато файлів, а Explorer відкриває папку, що містить ці файли.
бачить

3

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

    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та викликає функцію знову. A Thread.Sleep(0);не змінює ваші дозволи. Він повинен просто зареєструвати помилку і вийти з ладу витончено, в цей момент. І ця петля буде просто продовжуватися до тих пір, поки відкриється каталог (sub-) - він її не закриє програмно. Ми готові просто дозволити це робити до тих пір, поки ці речі не залишаться відкритими? Чи є кращий спосіб?
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

Чи можливо у вас є умова гонки, коли інший потік або процес додає файли до каталогу:

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

Процес видалення A:

  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 до відкритого для користувача папки: "Ви припиняєте!"
vapcguy

1

Виявляється, що для вибору шляху або підпапки у Провідника Windows достатньо блокувати єдине виконання Directory.Delete (шлях, правда), викинути IOException, як описано вище, і померти замість завантаження Windows Explorer у батьківську папку та виконувати процедуру як очікуваний.


Здається, це була моя проблема. Щойно я закрив Провідник і запустився знову, не виняток. Навіть вибір батьківського батька було недостатньо. Мені довелося фактично закрити Провідник.
Скотт Марлоу

Так, це відбувається і є причиною. Отже, будь-яка ідея, як програмно боротися з цим, чи завжди відповідь завжди впевнена, що всі 1000 користувачів закривають цю папку?
vapcguy

1

У мене сьогодні була ця проблема. Це сталося тому, що у мене було відкрито провідник Windows для каталогу, який намагався видалити, викликаючи рекурсивний виклик провалу, і, отже, IOException. Переконайтесь, що в каталозі немає відкритих ручок.

Крім того, MSDN зрозуміло, що вам не потрібно писати власне реколекцію: http://msdn.microsoft.com/en-us/library/fxeahc5f.aspx


1

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

Ми видаляли папку двійкових крапель на мережевій папці перед повторним створенням та повторно заповнювали її останніми бінарними файлами. Будь-яка інша збірка не зможе. Під час відкриття папки "drop" після невдалої збірки папка була порожньою, що вказує на те, що кожен аспект виклику Directory.Delete () був успішним, за винятком видалення власне каталогу.

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

Два можливих рішення в нашому випадку:

  • Створіть рекурсивне видалення у власному коді із затримками та перевірками між кожним кроком
  • Повторіть до X разів після IOException, даючи затримку перед повторною спробою

Останній метод швидкий і брудний, але, здається, він робить свою справу.


1

Це відбувається через FileChangesNotifications.

Це трапляється з ASP.NET 2.0. Якщо ви видалите деяку папку в додатку, вона буде перезапущена . Ви можете бачити це самостійно, використовуючи моніторинг здоров'я ASP.NET .

Просто додайте цей код у свій 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 свою конкретну роботу. Це може бути на одній машині (мангуста для 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 забезпечує очікувану функціональність оригінального / фондового каталогу.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. Працював нормально, коли я використав код Піта EXCEPT, який не обробляє, коли є заблоковані файли чи відкриті каталоги - тому він ніколи не потрапляє до ThreadPoolкоманди.
vapcguy

ПОПЕРЕДЖЕННЯ. Цю відповідь слід використовувати лише з рекурсивними = істинними. Якщо помилково, це перемістить каталог, навіть якщо він не порожній. Яка була б помилка; Правильна поведінка в такому випадку - кинути виняток і залишити каталог таким, яким він був.
ToolmakerSteve

1

Ця проблема може з’явитися в Windows, коли в каталозі (або в будь-якому підкаталозі) є файли, довжина шляху яких перевищує 260 символів.

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


1

Я вирішив цю тисячолітню техніку (ви можете залишити Thread.Sep самостійно в улові)

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 (рекурсивний: = 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, який діє як оператор, що використовує.


0

Вам не доведеться створювати і додаткові методи для рекурсивності або видалення файлів всередині папки додатково. Це все робиться автоматично, зателефонувавши

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? Це в будь-якому разі хороша загальна порада? (Я вірю вам, коли ви говорите, що у вас була проблема, і що ви використовували цей код, і вважаєте, що зараз у вас немає проблеми, але це просто не сенс)
Ruben Bartelink

@RubenBartelink Я згоден. Це злом. Код вуду, який щось робить, коли не зрозуміло, що він вирішує чи як. Я б хотів правильного рішення.
Реакційний

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

3
Використовуйте 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.