402 votes

Impossible de supprimer un répertoire avec Directory.Delete(path, true)

J'utilise .NET 3.5 et j'essaie de supprimer récursivement un répertoire en utilisant :

Directory.Delete(myPath, true);

D'après ce que j'ai compris, cette opération doit être rejetée si des fichiers sont en cours d'utilisation ou s'il y a un problème de permissions, mais sinon elle doit supprimer le répertoire et tout son contenu.

Cependant, j'obtiens parfois ceci :

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

Je ne suis pas surpris que la méthode puisse parfois échouer, mais je suis surpris de recevoir ce message particulier lorsque la méthode récursive est vraie. connaître le répertoire n'est pas vide).

Y a-t-il une raison pour que je voie ceci au lieu de AccessViolationException ?

14 votes

Vous ne verriez pas d'exception AccessViolationException -- c'est pour les opérations sur les pointeurs invalides, pas pour l'accès au disque.

1 votes

Il semble qu'il s'agisse d'un problème d'entrée-sortie autre que le fait que le répertoire ne soit pas vide, comme des poignées de fichiers ouvertes ou autre. J'essaierais d'utiliser l'option de suppression récursive, puis dans une capture pour IOException, de rechercher et de fermer tous les poignées de fichiers ouverts, puis de réessayer. Il y a une discussion à ce sujet ici : stackoverflow.com/questions/177146/

240voto

Jeremy Edwards Points 6999

Note de l'éditeur : Bien que cette réponse contienne des informations utiles, elle est incorrecte sur le plan factuel en ce qui concerne le fonctionnement de l'Union européenne. Directory.Delete . Veuillez lire les commentaires relatifs à cette réponse, ainsi que les autres réponses à cette question.


J'ai déjà rencontré ce problème.

L'origine du problème est que cette fonction ne supprime pas les fichiers qui se trouvent dans la structure du répertoire. Vous devez donc créer une fonction qui supprime tous les fichiers de la structure du répertoire, puis tous les répertoires avant de supprimer le répertoire lui-même. Je sais que cela va à l'encontre du deuxième paramètre, mais c'est une approche beaucoup plus sûre. En outre, vous voudrez probablement supprimer les attributs d'accès en LECTURE SEULE des fichiers juste avant de les supprimer. Dans le cas contraire, une exception sera levée.

Il suffit d'insérer ce code dans votre projet.

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

De plus, j'ajoute personnellement une restriction sur les zones de la machine qui sont autorisées à être supprimées car voulez-vous que quelqu'un appelle cette fonction sur C:\WINDOWS (%WinDir%) o C:\ .

4 votes

Sérieusement ? Cela semble être une extension .NET évidente. Pourquoi les concepteurs du cadre ont-ils choisi de l'omettre ?

1 votes

Merci au fait, c'était un copier/coller utile.

127 votes

C'est un non-sens. Directory.Delete(myPath, true) est une surcharge qui supprime tous les fichiers qui se trouvent dans la structure du répertoire. Si vous voulez vous tromper, faites-le avec la réponse de Ryan S.

192voto

ryascl Points 1006

Si vous essayez de supprimer récursivement un répertoire a et le répertoire a\b est ouvert dans l'Explorer, b sera supprimé mais vous obtiendrez l'erreur 'le répertoire n'est pas vide' pour a même si elle est vide quand on y va et qu'on la regarde. Le répertoire actuel de toute application (y compris l'Explorateur) conserve un handle sur le répertoire . Lorsque vous appelez Directory.Delete(true) il supprime de bas en haut : b entonces a . Si b est ouvert dans l'Explorateur, ce dernier détectera la suppression de l'adresse de l'utilisateur. b , changer de répertoire vers le haut cd .. et nettoyer les poignées ouvertes. Étant donné que le système de fichiers fonctionne de manière asynchrone, la fonction Directory.Delete échoue en raison de conflits avec Explorer.

Solution incomplète

J'ai initialement posté la solution suivante, avec l'idée d'interrompre le fil de discussion en cours pour laisser à Explorer le temps de libérer le gestionnaire de répertoire.

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

Mais cela ne fonctionne que si le répertoire ouvert est le immédiat enfant du répertoire que vous supprimez. Si a\b\c\d est ouvert dans Explorer et que vous l'utilisez sur a cette technique échouera après avoir supprimé d y c .

Une solution un peu meilleure

Cette méthode permet de gérer la suppression d'une structure de répertoire profonde, même si l'un des répertoires de niveau inférieur est ouvert dans l'Explorateur.

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

Malgré le travail supplémentaire que représente la récursion par nous-mêmes. toujours doivent gérer les UnauthorizedAccessException qui peuvent se produire en cours de route. Il n'est pas clair si la première tentative de suppression ouvre la voie à la deuxième tentative réussie, ou si c'est simplement le délai introduit par le lancement/la capture d'une exception qui permet au système de fichiers de rattraper son retard.

Vous pouvez peut-être réduire le nombre d'exceptions levées et rattrapées dans des conditions normales en ajoutant une fonction Thread.Sleep(0) au début de la try bloc. En outre, il existe un risque que, en cas de forte charge du système, vous puissiez traverser les deux blocs de l Directory.Delete tentent et échouent. Considérez cette solution comme un point de départ pour une suppression récursive plus robuste.

Réponse générale

Cette solution ne prend en compte que les particularités de l'interaction avec l'Explorateur Windows. Si vous voulez une opération de suppression solide comme le roc, il faut garder à l'esprit que n'importe quel élément (scanner de virus, etc.) peut avoir un accès ouvert à ce que vous essayez de supprimer, à tout moment. Vous devez donc réessayer plus tard. Le délai et le nombre de tentatives dépendent de l'importance de la suppression de l'objet. Comme MSDN indique ,

Un code robuste d'itération de fichiers doit prendre en compte de nombreuses complexités du système de fichiers.

Cette affirmation innocente, accompagnée d'un simple lien vers la documentation de référence NTFS, devrait vous faire dresser les cheveux sur la tête.

( Modifier : Beaucoup. Cette réponse n'avait à l'origine que la première solution, incomplète).

11 votes

Il semble que l'appel à Directory.Delete(path, true) alors que path ou l'un des dossiers/fichiers sous path est ouvert ou sélectionné dans l'Explorateur Windows déclenche une IOException. La fermeture de l'Explorateur Windows et la réexécution de mon code existant sans le try/catch suggéré ci-dessus ont bien fonctionné.

0 votes

Je pense que cela peut se produire même si l'explorateur n'est pas ouvert sur le répertoire. Je pense que cela peut être lié à un anti-virus qui y accède. Dans mon cas, la machine avait Norton.

1 votes

Je ne peux pas comprendre comment et pourquoi cela fonctionne, mais cela a fonctionné pour moi, alors que la définition des attributs de fichier et l'écriture de ma propre fonction récursive n'ont pas fonctionné.

45voto

Andrey Tarantsov Points 4487

Avant d'aller plus loin, vérifiez les raisons suivantes qui sont sous votre contrôle :

  • Le dossier est-il défini comme un répertoire courant de votre processus ? Si oui, changez-le d'abord en quelque chose d'autre.
  • Avez-vous ouvert un fichier (ou chargé une DLL) à partir de ce dossier ? (et oublié de le fermer/décharger)

Sinon, vérifiez les raisons légitimes suivantes, indépendantes de votre volonté :

  • Il y a des fichiers marqués comme étant en lecture seule dans ce dossier.
  • Vous n'avez pas l'autorisation de supprimer certains de ces fichiers.
  • Le fichier ou le sous-dossier est ouvert dans l'Explorateur ou une autre application.

Si l'un des cas ci-dessus est à l'origine du problème, vous devez comprendre pourquoi il se produit avant d'essayer d'améliorer votre code de suppression. Devrait votre application supprime des fichiers en lecture seule ou inaccessibles ? Qui les a marqués de cette façon, et pourquoi ?

Une fois que vous avez exclu les raisons ci-dessus, il reste la possibilité de défaillances intempestives. La suppression échouera si quelqu'un détient un handle sur l'un des fichiers ou dossiers à supprimer, et il existe de nombreuses raisons pour lesquelles quelqu'un peut énumérer le dossier ou lire ses fichiers :

  • indexeurs de recherche
  • anti-virus
  • logiciel de sauvegarde

L'approche générale pour traiter les échecs intempestifs consiste à essayer plusieurs fois, en faisant une pause entre les tentatives. Il est évident que vous ne voulez pas essayer indéfiniment, donc vous devriez abandonner après un certain nombre de tentatives et soit lancer une exception, soit ignorer l'erreur. Comme ceci :

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
}

À mon avis, une aide de ce type devrait être utilisée pour toutes les suppressions, car des échecs intempestifs sont toujours possibles. Cependant, VOUS DEVEZ ADAPTER CE CODE À VOTRE CAS D'UTILISATION, et non pas le copier aveuglément.

J'ai eu de faux échecs pour un dossier de données interne généré par mon application, situé sous %LocalAppData%, et mon analyse est donc la suivante :

  1. Le dossier est contrôlé uniquement par mon application, et l'utilisateur n'a aucune raison valable d'aller marquer des choses comme étant en lecture seule ou inaccessibles à l'intérieur de ce dossier, donc je n'essaie pas de gérer ce cas.

  2. Il n'y a pas de contenu de valeur créé par l'utilisateur, il n'y a donc pas de risque de suppression forcée par erreur.

  3. Comme il s'agit d'un dossier de données interne, je ne m'attends pas à ce qu'il soit ouvert dans l'explorateur, du moins je ne ressens pas le besoin de traiter spécifiquement ce cas (c'est-à-dire que je peux très bien traiter ce cas via le support).

  4. Si toutes les tentatives échouent, je choisis d'ignorer l'erreur. Dans le pire des cas, l'application ne parvient pas à décompresser certaines ressources plus récentes, se bloque et invite l'utilisateur à contacter l'assistance, ce qui est acceptable pour moi tant que cela ne se produit pas souvent. Ou, si l'application ne plante pas, elle laisse derrière elle d'anciennes données, ce qui est également acceptable pour moi.

  5. Je choisis de limiter les tentatives à 500 ms (50 * 10). Il s'agit d'un seuil arbitraire qui fonctionne dans la pratique ; je voulais que le seuil soit suffisamment court pour que les utilisateurs ne tuent pas l'application en pensant qu'elle a cessé de répondre. D'un autre côté, une demi-seconde est largement suffisante pour que le délinquant finisse de traiter mon dossier. A en juger par les autres réponses de l'OS qui trouvent parfois même Sleep(0) pour être acceptable, très peu d'utilisateurs auront à effectuer plus d'une seule tentative.

  6. Je réessaie toutes les 50 ms, ce qui est un autre nombre arbitraire. J'estime que si un fichier est en cours de traitement (indexation, vérification) lorsque j'essaie de le supprimer, 50 ms est à peu près le bon délai pour que le traitement soit terminé dans mon cas. De plus, 50 ms est un délai suffisamment court pour ne pas entraîner de ralentissement notable ; encore une fois, Sleep(0) semble être suffisante dans de nombreux cas, nous ne voulons pas trop tarder.

  7. Le code réessaie sur toutes les exceptions IO. Je ne m'attends normalement pas à ce qu'il y ait des exceptions lors de l'accès à %LocalAppData%, j'ai donc choisi la simplicité et accepté le risque d'un délai de 500 ms au cas où une exception légitime se produirait. Je n'ai pas non plus voulu trouver un moyen de détecter l'exception exacte sur laquelle je veux réessayer.

8 votes

P.P.S. Quelques mois plus tard, je suis heureux d'annoncer que ce morceau de code (un peu fou) a complètement résolu le problème. Les demandes d'assistance concernant ce problème sont tombées à zéro (contre 1-2 par semaine).

1 votes

+Bien qu'il s'agisse d'une solution plus robuste et moins "voilà, c'est la solution parfaite pour vous" que l'approche de la Commission européenne. stackoverflow.com/a/7518831/11635 Pour moi, la même chose s'applique - programmation par coïncidence - à manipuler avec précaution. Un point utile de votre code est que si vous allez faire une nouvelle tentative, vous devez considérer que vous êtes dans une course avec l'ambiguïté de savoir si le répertoire a "disparu" depuis la dernière tentative [et un niaf Directory.Exists la garde ne résoudrait pas ce problème].

1 votes

J'adore ... je ne sais pas ce que je fais pour que ce soit toujours un point sensible pour moi ... mais ce n'est pas parce que j'ai le répertoire ouvert dans l'explorateur ... il n'y a pas beaucoup d'agitation sur internet à propos de ce plus ou moins bug ... au moins moi et Andrey avons un moyen de le gérer :)

20voto

Rehan Saeed Points 125

Réponse asynchrone moderne

La réponse acceptée est tout simplement fausse, cela peut fonctionner pour certaines personnes parce que le temps pris pour récupérer les fichiers sur le disque libère ce qui bloquait les fichiers. Le fait est que cela se produit parce que les fichiers sont verrouillés par un autre processus/flux/action. Les autres réponses utilisent Thread.Sleep (Beurk) pour réessayer de supprimer le répertoire après un certain temps. Cette question doit être réexaminée avec une réponse plus moderne.

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

Tests unitaires

Ces tests montrent un exemple de la façon dont un fichier verrouillé peut provoquer le blocage de l'ordinateur. Directory.Delete d'échouer et comment le TryDeleteDirectory La méthode ci-dessus résout le problème.

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

0 votes

Pouvez-vous préciser ce que vous entendez par "moderne" ? Quels sont les avantages de votre approche ? Pourquoi les autres sont-elles, à votre avis, mauvaises ?

3 votes

Les autres n'ont pas tort. Ils utilisent simplement des API plus anciennes comme Thread.Sleep que vous devriez éviter aujourd'hui et utiliser async / await con Task.Delay à la place. C'est compréhensible, c'est une question très ancienne.

0 votes

Cette approche ne fonctionnera pas en VB.Net (du moins pas avec une conversion très littérale ligne par ligne) pour les raisons suivantes BC36943 'Await' cannot be used inside a 'Catch' statement, a 'Finally' statement, or a 'SyncLock' statement.

18voto

jettatore Points 364

Il convient de mentionner une chose importante (je l'aurais ajouté en tant que commentaire mais je ne suis pas autorisé à le faire) : le comportement de la surcharge a changé entre .NET 3.5 et .NET 4.0.

Directory.Delete(myPath, true);

À partir de .NET 4.0, il supprime les fichiers dans le dossier lui-même, mais PAS en 3.5. Ceci peut être vu dans la documentation MSDN également.

.NET 4.0

Supprime le répertoire spécifié et, si indiqué, tous les sous-répertoires et fichiers du répertoire.

.NET 3.5

Supprime un répertoire vide et, si indiqué, tous les sous-répertoires et fichiers du répertoire.

3 votes

Je pense qu'il s'agit seulement d'un changement de documentation... si cela supprime seulement un "répertoire vide", qu'est-ce que cela signifierait de supprimer aussi les fichiers dans le répertoire, avec le 2° paramètre ? S'il est vide, il n'y a pas de fichiers...

0 votes

Je crains que vous ne fassiez de fausses suppositions. J'ai posté ceci après avoir testé le code avec les deux versions du framework. La suppression d'un dossier non vide dans la version 3.5 déclenche une exception.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X