422 Stimmen

Verzeichnis kann nicht mit Directory.Delete(path, true) gelöscht werden

Ich verwende .NET 3.5 und versuche, ein Verzeichnis mit rekursiv zu löschen:

Directory.Delete(myPath, true);

Meines Erachtens sollte dies zu einem Fehler führen, wenn Dateien in Gebrauch sind oder ein Problem mit den Zugriffsrechten besteht, aber ansonsten sollten das Verzeichnis und sein gesamter Inhalt gelöscht werden.

Gelegentlich erhalte ich jedoch diese Meldung:

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)
    ...

Ich bin nicht überrascht, dass die Methode manchmal abbricht, aber ich bin überrascht, dass ich diese spezielle Meldung erhalte, wenn rekursiv wahr ist (ich wissen das Verzeichnis ist nicht leer.)

Gibt es einen Grund, warum ich dies anstelle von AccessViolationException sehen würde?

245voto

Jeremy Edwards Punkte 14304

Anmerkung der Redaktion: Obwohl diese Antwort einige nützliche Informationen enthält, ist sie sachlich falsch in Bezug auf die Funktionsweise von Directory.Delete . Bitte lesen Sie die Kommentare zu dieser Antwort und zu anderen Antworten auf diese Frage.


Ich hatte dieses Problem schon einmal.

Die Wurzel des Problems ist, dass diese Funktion keine Dateien löscht, die sich innerhalb der Verzeichnisstruktur befinden. Was Sie also tun müssen, ist eine Funktion zu erstellen, die alle Dateien innerhalb der Verzeichnisstruktur und dann alle Verzeichnisse löscht, bevor Sie das Verzeichnis selbst entfernen. Ich weiß, dass dies gegen den zweiten Parameter verstößt, aber es ist eine viel sicherere Methode. Außerdem sollten Sie wahrscheinlich die READ-ONLY-Zugriffsattribute aus den Dateien entfernen, bevor Sie sie löschen. Andernfalls wird dies eine Ausnahme auslösen.

Fügen Sie diesen Code einfach in Ihr Projekt ein.

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);
}

Außerdem füge ich persönlich eine Beschränkung der Bereiche des Rechners hinzu, die gelöscht werden dürfen, denn wollen Sie, dass jemand diese Funktion aufruft C:\WINDOWS (%WinDir%) o C:\ .

202voto

rpisryan Punkte 2349

Wenn Sie versuchen, ein Verzeichnis rekursiv zu löschen a und Verzeichnis a\b im Explorer geöffnet ist, b wird gelöscht, aber Sie erhalten die Fehlermeldung 'Verzeichnis ist nicht leer' für a auch wenn sie leer ist, wenn man nachsieht. Das aktuelle Verzeichnis einer beliebigen Anwendung (einschließlich Explorer) behält einen Handle auf das Verzeichnis . Wenn Sie anrufen Directory.Delete(true) löscht sie von unten nach oben: b entonces a . Wenn b im Explorer geöffnet ist, erkennt der Explorer die Löschung von b , Verzeichnis nach oben wechseln cd .. und reinigen Sie offene Griffe. Da das Dateisystem asynchron arbeitet, ist die Directory.Delete schlägt aufgrund von Konflikten mit dem Explorer fehl.

Unvollständige Lösung

Ursprünglich hatte ich die folgende Lösung gepostet, mit der Idee, den aktuellen Thread zu unterbrechen, damit Explorer Zeit hat, das Verzeichnis-Handle freizugeben.

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

Dies funktioniert jedoch nur, wenn das geöffnete Verzeichnis das sofort Unterverzeichnis des zu löschenden Verzeichnisses. Wenn a\b\c\d im Explorer geöffnet ist und Sie diesen auf a scheitert diese Technik nach dem Löschen von d y c .

Eine etwas bessere Lösung

Mit dieser Methode kann eine tiefe Verzeichnisstruktur auch dann gelöscht werden, wenn eines der untergeordneten Verzeichnisse im Explorer geöffnet ist.

/// <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);
    }
}

Trotz des zusätzlichen Aufwands, den die Wiederholungsprüfung für uns mit sich bringt, haben wir todavía müssen sich um die UnauthorizedAccessException die auf dem Weg dorthin auftreten können. Es ist nicht klar, ob der erste Löschversuch den Weg für den zweiten, erfolgreichen Löschversuch ebnet, oder ob das Dateisystem durch die Verzögerung, die durch das Auslösen/Auffangen einer Ausnahme entsteht, die Möglichkeit hat, den Rückstand aufzuholen.

Sie können die Anzahl der ausgelösten und abgefangenen Ausnahmen unter typischen Bedingungen verringern, indem Sie eine Thread.Sleep(0) zu Beginn der try Block. Darüber hinaus besteht die Gefahr, dass Sie bei starker Systembelastung durch beide Blöcke fliegen. Directory.Delete versuchen und scheitern. Betrachten Sie diese Lösung als Ausgangspunkt für eine robustere rekursive Löschung.

Allgemeine Antwort

Diese Lösung befasst sich nur mit den Besonderheiten der Interaktion mit dem Windows Explorer. Wenn Sie einen felsenfesten Löschvorgang wünschen, sollten Sie bedenken, dass alles (Virenscanner, was auch immer) jederzeit einen offenen Zugriff auf das, was Sie zu löschen versuchen, haben könnte. Sie müssen es also später noch einmal versuchen. Wie viel später und wie oft Sie es versuchen, hängt davon ab, wie wichtig es ist, dass das Objekt gelöscht wird. Wie MSDN zeigt an ,

Ein robuster Datei-Iterationscode muss viele Komplexitäten berücksichtigen des Dateisystems berücksichtigen.

Diese unschuldige Aussage, die nur mit einem Link zur NTFS-Referenzdokumentation versehen ist, sollte Ihnen die Haare zu Berge stehen lassen.

( 編集 : Sehr viel. Diese Antwort enthielt ursprünglich nur die erste, unvollständige Lösung).

50voto

Andrey Tarantsov Punkte 8771

Bevor Sie weitermachen, überprüfen Sie die folgenden Gründe, die unter Ihrer Kontrolle liegen:

  • Ist der Ordner als aktuelles Verzeichnis Ihres Prozesses festgelegt? Wenn ja, ändern Sie ihn zunächst in ein anderes Verzeichnis.
  • Haben Sie eine Datei (oder eine DLL) aus diesem Ordner geöffnet (und vergessen, sie zu schließen/zu entladen)?

Andernfalls prüfen Sie, ob es berechtigte Gründe gibt, auf die Sie keinen Einfluss haben:

  • In diesem Ordner befinden sich Dateien, die als schreibgeschützt gekennzeichnet sind.
  • Sie haben keine Löschberechtigung für einige dieser Dateien.
  • Die Datei oder der Unterordner ist im Explorer oder einer anderen Anwendung geöffnet.

Wenn eines der oben genannten Probleme auftritt, sollten Sie verstehen, warum es passiert, bevor Sie versuchen, Ihren Löschcode zu verbessern. Sollte Ihre Anwendung schreibgeschützte oder unzugängliche Dateien löschen? Wer hat sie so markiert, und warum?

Wenn Sie die oben genannten Gründe ausgeschlossen haben, besteht immer noch die Möglichkeit von Fehlfunktionen. Der Löschvorgang schlägt fehl, wenn jemand ein Handle auf eine der zu löschenden Dateien oder Ordner besitzt, und es gibt viele Gründe, warum jemand den Ordner aufzählen oder seine Dateien lesen könnte:

  • Suchindexierer
  • Antivirenmittel
  • Sicherungssoftware

Die allgemeine Vorgehensweise bei Fehlversuchen besteht darin, es mehrmals zu versuchen und zwischen den Versuchen eine Pause einzulegen. Sie wollen es natürlich nicht ewig versuchen, also sollten Sie nach einer bestimmten Anzahl von Versuchen aufgeben und entweder eine Ausnahme auslösen oder den Fehler ignorieren. Zum Beispiel so:

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
}

Meiner Meinung nach sollte ein solches Hilfsmittel für alle Löschungen verwendet werden, da Fehlschläge immer möglich sind. Allerdings sollten Sie diesen Code an Ihren Anwendungsfall anpassen und ihn nicht einfach blind kopieren.

Ich hatte falsche Fehler für einen internen Datenordner, der von meiner Anwendung generiert wurde und sich unter %LocalAppData% befindet, daher geht meine Analyse wie folgt:

  1. Der Ordner wird ausschließlich von meiner Anwendung kontrolliert, und der Benutzer hat keinen triftigen Grund, Dinge in diesem Ordner als schreibgeschützt oder unzugänglich zu markieren.

  2. Es gibt dort keine wertvollen, vom Benutzer erstellten Daten, so dass keine Gefahr besteht, dass etwas versehentlich gelöscht wird.

  3. Da es sich um einen internen Datenordner handelt, erwarte ich nicht, dass er im Explorer geöffnet ist, zumindest sehe ich keine Notwendigkeit, diesen Fall speziell zu behandeln (d. h. ich kann diesen Fall über den Support bearbeiten).

  4. Wenn alle Versuche fehlschlagen, ignoriere ich die Fehlermeldung. Im schlimmsten Fall gelingt es der App nicht, einige neuere Ressourcen zu entpacken, sie stürzt ab und fordert den Benutzer auf, den Support zu kontaktieren, was für mich akzeptabel ist, solange es nicht häufig vorkommt. Oder, wenn die Anwendung nicht abstürzt, lässt sie einige alte Daten zurück, was ebenfalls für mich akzeptabel ist.

  5. Ich habe mich dafür entschieden, die Wiederholungsversuche auf 500ms (50 * 10) zu begrenzen. Dies ist ein willkürlicher Schwellenwert, der in der Praxis funktioniert; ich wollte, dass die Schwelle kurz genug ist, damit die Benutzer die Anwendung nicht beenden, weil sie denken, dass sie nicht mehr reagiert. Andererseits ist eine halbe Sekunde genug Zeit für den Täter, um die Bearbeitung meines Ordners zu beenden. Nach den Antworten anderer SO zu urteilen, die manchmal sogar Sleep(0) für akzeptabel halten, werden nur sehr wenige Benutzer jemals mehr als einen Wiederholungsversuch benötigen.

  6. Ich versuche es alle 50 ms erneut, was eine weitere willkürliche Zahl ist. Wenn eine Datei gerade verarbeitet (indiziert, geprüft) wird, wenn ich versuche, sie zu löschen, sind 50 ms meiner Meinung nach die richtige Zeit, um zu erwarten, dass die Verarbeitung in meinem Fall abgeschlossen ist. Außerdem sind 50 ms klein genug, um nicht zu einer spürbaren Verlangsamung zu führen; nochmals, Sleep(0) scheint in vielen Fällen auszureichen, so dass wir nicht zu viel Zeit verstreichen lassen wollen.

  7. Bei einer IO-Ausnahme wird der Code wiederholt. Normalerweise erwarte ich keine Ausnahmen beim Zugriff auf %LocalAppData%, also entschied ich mich für die Einfachheit und nahm das Risiko einer Verzögerung von 500 ms in Kauf, falls eine legitime Ausnahme auftritt. Ich wollte auch nicht herausfinden, wie ich die genaue Ausnahme erkennen kann, bei der ich den Versuch wiederholen möchte.

24voto

Muhammad Rehan Saeed Punkte 31864

Moderne asynchrone Antwort

Die gängige Antwort ist schlichtweg falsch. Für manche Leute mag es funktionieren, weil die Zeit, die benötigt wird, um die Dateien von der Festplatte zu holen, das freisetzt, was die Dateien blockiert hat. Tatsache ist, dass dies geschieht, weil die Dateien durch einen anderen Prozess/Strom/Aktion gesperrt werden. Die anderen Antworten verwenden Thread.Sleep (Igitt), um nach einiger Zeit erneut zu versuchen, das Verzeichnis zu löschen. Diese Frage muss mit einer moderneren Antwort überdacht werden.

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;
}

Einheitliche Tests

Diese Tests zeigen ein Beispiel dafür, wie eine gesperrte Datei die Directory.Delete zu scheitern und wie die TryDeleteDirectory Methode behebt das Problem.

[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);
        }
    }
}

20voto

jettatore Punkte 364

Eine wichtige Sache, die erwähnt werden sollte (ich würde sie als Kommentar hinzufügen, aber das darf ich nicht), ist, dass sich das Verhalten der Überladung von .NET 3.5 auf .NET 4.0 geändert hat.

Directory.Delete(myPath, true);

Ab .NET 4.0 werden die Dateien im Ordner selbst gelöscht, aber NICHT in 3.5. Dies kann auch in der MSDN-Dokumentation nachgelesen werden.

.NET 4.0

Löscht das angegebene Verzeichnis und, falls angegeben, alle Unterverzeichnisse und Dateien in diesem Verzeichnis.

.NET 3.5

Löscht ein leeres Verzeichnis und, falls angegeben, alle Unterverzeichnisse und Dateien in diesem Verzeichnis.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X