web-dev-qa-db-fra.com

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

J'utilise .NET 3.5, essayant de supprimer récursivement un répertoire en utilisant:

Directory.Delete(myPath, true);

Si j'ai bien compris, cela devrait se produire si des fichiers sont en cours d'utilisation ou s'il y a un problème d'autorisations, mais sinon, le répertoire et tout son contenu devraient être supprimés.

Cependant, je reçois 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 jette parfois, mais je suis surpris de recevoir ce message particulier quand récursif est vrai. (I savoir le répertoire n'est pas vide.)

Y a-t-il une raison pour laquelle je verrais cela à la place de AccessViolationException?

363
Jason Anderson

Note de la rédaction: Bien que cette réponse contienne des informations utiles, elle est factuellement incorrecte sur le fonctionnement de Directory.Delete. S'il vous plaît lire les commentaires pour cette réponse, et d'autres réponses à cette question.


J'ai rencontré ce problème avant.

La racine du problème est que cette fonction ne supprime pas les fichiers qui se trouvent dans la structure de répertoires. Vous devez donc créer une fonction qui supprime tous les fichiers de la structure de répertoires, 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 souhaiterez probablement supprimer les attributs d'accès en lecture seule des fichiers juste avant de les supprimer. Sinon, cela déclenchera une exception.

Il suffit de glisser 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);
}

En outre, pour moi, j’ajoute personnellement une restriction sur les zones de la machine qui sont autorisées à être supprimées parce que vous voulez que quelqu'un appelle cette fonction sur C:\WINDOWS (%WinDir%) ou C:\.

222
Jeremy Edwards

Si vous essayez de supprimer de manière récursive le répertoire a et que le répertoire a\b est ouvert dans l'Explorateur, b sera supprimé, mais vous obtiendrez le message d'erreur "répertoire n'est pas vide" pour a même s'il est vide lorsque vous y allez. Le répertoire actuel de n’importe quelle application (y compris l’Explorateur) conserve un descripteur dans le répertoire . Lorsque vous appelez Directory.Delete(true), il supprime de haut en bas: b, puis a. Si b est ouvert dans Explorer, Explorer détecte la suppression de b, modifie le répertoire vers le haut cd .. et nettoie les descripteurs ouverts. Étant donné que le système de fichiers fonctionne de manière asynchrone, l'opération Directory.Delete échoue en raison de conflits avec Explorer.

Solution incomplète

A l'origine, j'avais posté la solution suivante, avec l'idée d'interrompre le thread en cours pour permettre à Explorer de libérer le descripteur 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 open est l’enfant immédiat du répertoire que vous supprimez. Si a\b\c\d est ouvert dans l'Explorateur et que vous l'utilisez sur a, cette technique échouera après la suppression de d et c.

Une solution un peu meilleure

Cette méthode gérera la suppression d'une structure de répertoires en profondeur même si l'un des répertoires de niveau inférieur est ouvert dans Explorer.

/// <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 de récursivité nous-mêmes, nous devons toujours gérer le UnauthorizedAccessException qui peut se produire en cours de route. Il n'est pas clair si la première tentative de suppression ouvre la voie à la seconde, la première, ou s'il s'agit simplement du délai de temporisation introduit par la levée/interception d'une exception qui permet au système de fichiers de se rattraper.

Vous pourrez peut-être réduire le nombre d'exceptions levées et interceptées dans des conditions normales en ajoutant un Thread.Sleep(0) au début du bloc try. De plus, il y a un risque que, sous une charge système importante, vous puissiez survoler les deux tentatives Directory.Delete et échouer. Considérez cette solution comme un point de départ pour une suppression récursive plus robuste.

Réponse générale

Cette solution n'aborde que les particularités de l'interaction avec Windows Explorer. Si vous souhaitez une opération de suppression à toute épreuve, il ne faut pas oublier que tout (scanner de virus, etc.) peut avoir un accès libre à ce que vous essayez de supprimer, à tout moment. Donc, vous devez réessayer plus tard. Le nombre de tentatives ultérieures et de tentatives ultérieures dépend de l’importance de la suppression de l’objet. Comme MSDN indique ,

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

Cette déclaration innocente, fournie uniquement avec un lien vers la documentation de référence NTFS, devrait permettre à votre chevelure de se lever.

( Edit : Beaucoup. Cette réponse ne contenait à l'origine que la première solution incomplète.)

171
ryascl

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

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

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

  • Il existe des fichiers marqués en lecture seule dans ce dossier.
  • Vous ne disposez pas d'une autorisation de suppression pour certains de ces fichiers.
  • Le fichier ou le sous-dossier est ouvert dans l'explorateur ou une autre application.

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

Une fois que vous avez écarté les raisons ci-dessus, il existe toujours une possibilité de défaillances parasites. La suppression échouera si quelqu'un détient un identifiant sur l'un des fichiers ou dossiers en cours de suppression, et il existe de nombreuses raisons pour lesquelles une personne peut énumérer le dossier ou lire ses fichiers:

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

L’approche générale pour faire face aux défaillances parasites consiste à essayer plusieurs fois, en faisant une pause entre les tentatives. Évidemment, vous ne voulez pas continuer à essayer pour toujours, vous devriez donc abandonner après un certain nombre de tentatives et renvoyer une exception ou ignorer l'erreur. Comme ça:

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, un assistant de ce type devrait être utilisé pour toutes les suppressions, car de fausses défaillances sont toujours possibles. Cependant, vous devez adapter ce code à votre cas d'utilisation et non pas le copier aveuglément.

J'ai rencontré des échecs parasites pour un dossier de données interne généré par mon application, situé sous% LocalAppData%. Mon analyse est la suivante:

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

  2. Il n'y a pas de contenu précieux créé par l'utilisateur, il n'y a donc aucun risque d'effacer de force une suppression par erreur.

  3. Étant un dossier de données interne, je ne m'attends pas à ce qu'il soit ouvert dans Explorer, du moins je ne ressens pas le besoin de traiter spécifiquement le cas (c'est-à-dire que je peux très bien gérer 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 de nouvelles ressources, se bloque et invite l'utilisateur à contacter l'assistance, ce qui est acceptable pour moi tant que cela n'arrive pas souvent. Ou, si l'application ne plante pas, elle laissera des données anciennes, ce qui est à nouveau acceptable pour moi.

  5. Je choisis de limiter les tentatives à 500 ms (50 * 10). C'est 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, pensant qu'il a cessé de répondre. D'autre part, une demi-seconde est suffisamment de temps pour que le délinquant termine le traitement de mon dossier. À en juger par les SO réponses qui trouvent parfois même Sleep(0) pour être acceptable, très peu d'utilisateurs rencontrent plus d'une tentative.

  6. Je réessaye toutes les 50ms, ce qui est un autre nombre arbitraire. Je pense que si un fichier est en cours de traitement (indexé, coché) lorsque je tente de le supprimer, 50 ms est à peu près le bon moment pour attendre que le traitement soit terminé dans mon cas. En outre, 50 ms est suffisamment petit pour ne pas entraîner de ralentissement notable; encore une fois, Sleep(0) semble suffire dans de nombreux cas, nous ne voulons donc pas trop attendre.

  7. Le code réessaie sur toutes les exceptions IO. Je ne m'attends pas normalement à ce que des exceptions accèdent à% LocalAppData%. J'ai donc choisi la simplicité et accepté le risque d'un retard de 500 ms en cas d'exception légitime. Je ne voulais pas non plus trouver un moyen de détecter l'exception exacte sur laquelle je veux réessayer.

40
Andrey Tarantsov

Une chose importante à mentionner (je l'avais ajouté sous forme de commentaire mais je ne suis pas autorisé à le faire) est que le comportement de la surcharge est passé de .NET 3.5 à .NET 4.0.

Directory.Delete(myPath, true);

À partir de .NET 4.0, il supprime les fichiers dans le dossier lui-même, mais PAS dans 3.5. Cela se voit également dans la documentation MSDN.

. NET 4.

Supprime le répertoire spécifié et, le cas échéant, les sous-répertoires et les fichiers du répertoire.

.NET 3.5

Supprime n répertoire vide et, le cas échéant, les sous-répertoires et fichiers du répertoire.

15
jettatore

J'ai eu le même problème sous Delphi. Et le résultat final était que ma propre application était en train de verrouiller le répertoire que je voulais supprimer. D'une manière ou d'une autre, le répertoire s'est verrouillé lorsque j'écrivais dessus (certains fichiers temporaires).

Le catch 22 était, j'ai fait un simple changement de répertoire à son parent avant de le supprimer.

14
Drejc

Réponse async moderne

La réponse acceptée est tout simplement fausse, cela pourrait fonctionner pour certaines personnes car le temps nécessaire pour obtenir des fichiers à partir du disque libère tout ce qui était verrouillé. 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 montrant comment un fichier verrouillé peut entraîner l'échec de Directory.Delete et comment la méthode TryDeleteDirectory 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);
        }
    }
}
13

Vous pouvez reproduire l'erreur en exécutant:

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

Lorsque vous essayez de supprimer le répertoire 'b', il génère l'exception IOException "Le répertoire n'est pas vide". C'est stupide puisque nous venons de supprimer le répertoire 'c'.

D'après ma compréhension, l'explication est que le répertoire 'c' est marqué comme supprimé. Mais la suppression n'est pas encore validée dans le système. Le système a répondu que le travail est terminé, alors que le traitement est toujours en cours. Le système attend probablement que l'explorateur de fichiers se concentre sur le répertoire parent pour valider la suppression.

Si vous examinez le code source de la fonction Delete ( http://referencesource.Microsoft.com/#mscorlib/system/io/directory.cs ), vous verrez qu'il utilise le Win32Native.RemoveDirectory natif. une fonction. Ce comportement à ne pas attendre est noté ici:

La fonction RemoveDirectory marque un répertoire à supprimer à la fermeture. Par conséquent, le répertoire n'est pas supprimé jusqu'à la fermeture du dernier descripteur du répertoire.

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

Dormir et réessayer est la solution. Cf la solution de ryascl.

11
Olivier de Rivoyre

Je suis surpris que personne n'ait pensé à cette méthode simple, non récursive, qui permet de supprimer des répertoires contenant des fichiers en lecture seule, sans avoir à modifier l'attribut en lecture seule de chacun d'eux.

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

(Changez un peu pour ne pas ouvrir momentanément une fenêtre cmd, qui est disponible partout sur Internet)

11
Piyush Soni

J'ai eu un de ces problèmes de permission étranges lors de la suppression des répertoires du profil utilisateur (dans C:\Documents and Settings) alors que je pouvais le faire dans l'explorateur Shell.

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

Cela n'a aucun sens pour moi ce qu'une opération "fichier" fait sur un répertoire, mais je sais que cela fonctionne et que ça me suffit!

7
Nick

Cette réponse est basée sur: https://stackoverflow.com/a/1703799/184528 . La différence avec mon code est que nous ne renvoyons que de nombreux sous-répertoires et fichiers de suppression lorsque cela est nécessaire. Un appel à Directory.Delete échoue lors d'une première tentative (ce qui peut arriver du fait que Windows Explorer recherche un répertoire).

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

La suppression récursive de répertoires qui ne supprime pas les fichiers est certainement inattendue. Mon correctif pour cela:

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

J'ai rencontré des cas où cela aidait, mais en général, Directory.Delete supprime les fichiers dans les répertoires lors d'une suppression récursive, comme documenté dans msdn .

De temps en temps, je rencontre également ce comportement irrégulier en tant qu'utilisateur de l'Explorateur Windows: Parfois, je ne peux pas supprimer un dossier (le message insensé est considéré comme un "accès refusé"), mais lorsque j'explore et supprime les éléments inférieurs, je peux ensuite supprimer les éléments supérieurs. articles aussi bien. Je suppose donc que le code ci-dessus traite d'une anomalie du système d'exploitation, et non d'un problème de bibliothèque de classe de base.

2
citykid

J'ai passé quelques heures à résoudre ce problème et d'autres exceptions lors de la suppression du répertoire. C'est ma solution

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

Ce code a le petit délai, ce qui n’est pas important pour mon application. Mais attention, le retard peut être un problème pour vous si vous avez beaucoup de sous-répertoires dans le répertoire que vous souhaitez supprimer.

2
Demid

Est-il possible que vous ayez une situation de concurrence critique lorsqu'un autre processus ou processus ajoute des fichiers au répertoire:

La séquence serait:

Processus Deleter A:

  1. Vider le répertoire
  2. Supprimez le répertoire (maintenant vide).

Si quelqu'un d'autre ajoute un fichier entre 1 et 2, alors peut-être 2 lancera-t-il l'exception listée?

2
Douglas Leeder

Aucune des solutions ci-dessus a bien fonctionné pour moi. J'ai fini par utiliser une version modifiée de la solution @ryascl comme ci-dessous:

    /// <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
cahit beyaz

J'ai eu ce problème aujourd'hui. Cela se passait parce que Windows Explorer était ouvert sur le répertoire qui essayait d'être supprimé, ce qui provoquait l'appel récursif d'échec et donc l'exception IOException. Assurez-vous qu'il n'y a pas de descripteurs ouverts dans le répertoire.

En outre, MSDN indique clairement que vous ne devez pas écrire votre propre récusion: http://msdn.Microsoft.com/en-us/library/fxeahc5f.aspx

1
GrokSrc

Il semble qu’avoir le chemin ou le sous-dossier sélectionné dans l’explorateur Windows soit suffisant pour bloquer une exécution unique de Directory.Delete (chemin, true), en lançant une exception IO comme décrit ci-dessus et en mourant au lieu de démarrer l’explorateur Windows dans un dossier parent et en procédant comme suit: attendu.

1
David Alpert

Ce problème peut apparaître sous Windows lorsqu'il existe des fichiers dans un répertoire (ou dans un sous-répertoire) dont la longueur du chemin d'accès est supérieure à 260 symboles.

Dans ce cas, vous devez supprimer \\\\?\C:\mydir au lieu de C:\mydir. Sur la limite de 260 symboles, vous pouvez lire ici .

1
HostageBrain

J'ai eu le même problème avec Windows Workflow Foundation sur un serveur de génération avec TFS2012. En interne, le flux de travail appelé Directory.Delete () avec l'indicateur récursif défini sur true. Cela semble être lié au réseau dans notre cas.

Nous supprimions un dossier de dépôt binaire sur un partage réseau avant de le recréer et de le re-remplir avec les derniers fichiers binaires. Toute autre construction échouerait. Lors de l'ouverture du dossier de dépôt après une génération ayant échoué, le dossier était vide, ce qui indique que tous les aspects de l'appel Directory.Delete () ont réussi, à l'exception de la suppression du répertoire.

Le problème semble être dû à la nature asynchrone des communications de fichiers réseau. Le serveur de construction a dit au serveur de fichiers de supprimer tous les fichiers et le serveur de fichiers l'a signalé, même s'il n'était pas complètement terminé. Ensuite, le serveur de construction a demandé que le répertoire soit supprimé et le serveur de fichiers a rejeté la demande car la suppression des fichiers n’était pas complètement terminée.

Deux solutions possibles dans notre cas:

  • Construisez la suppression récursive dans notre propre code avec des retards et des vérifications entre chaque étape
  • Réessayez jusqu'à X fois après une exception IOException, en laissant un délai avant de réessayer.

Cette dernière méthode est rapide et sale, mais semble faire l'affaire.

1
Shaun

Le répertoire ou un fichier qu'il contient est verrouillé et ne peut pas être supprimé. Trouvez le coupable qui le verrouille et voyez si vous pouvez l'éliminer.

1
Vilx-

Ceci est dû à FileChangesNotifications.

Cela se produit depuis ASP.NET 2.0. Lorsque vous supprimez un dossier dans une application, il est redémarré . Vous pouvez le voir vous-même à l'aide de Surveillance de l'état de santé ASP.NET .

Ajoutez simplement ce code à votre web.config/configuration/system.web:

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


Après cela, vérifiez Windows Log -> Application. Que se passe-t-il:

Lorsque vous supprimez un dossier, s'il existe un sous-dossier, Delete(path, true) supprime d'abord le sous-dossier. Il suffit que FileChangesMonitor connaisse la suppression et ferme votre application. Pendant ce temps, votre répertoire principal n'est pas encore supprimé. C'est l'événement de Log:


enter image description here


Delete() n'a pas fini son travail et parce que l'application est en train de s'éteindre, elle déclenche une exception:

enter image description here

Lorsque vous n'avez aucun sous-dossier dans un dossier que vous supprimez, Delete () supprime simplement tous les fichiers et ce dossier, l'application est également relancée, mais vous ne recevez aucune exception , car le redémarrage de l'application n'interrompt rien. Néanmoins, vous perdez toutes les sessions en cours de processus, l'application ne répond pas aux demandes lors du redémarrage, etc.

Et maintenant?

Il existe des solutions de contournement et des ajustements pour désactiver ce comportement, jonction d'annuaire , désactivation de FCN avec le registre , arrêt de FileChangesMonitor à l'aide de Reflection (car il n'y a pas de méthode exposée) , mais ils ne semblent pas tous avoir raison, car FCN est là pour une raison. Il s’occupe de la structure de votre application , qui n’est pas la structure de vos données . La réponse courte est: placez les dossiers que vous souhaitez supprimer en dehors de votre application. FileChangesMonitor ne recevra aucune notification et votre application ne sera pas redémarrée à chaque fois. Vous n'obtiendrez aucune exception. Pour les rendre visibles sur le Web, il existe deux méthodes:

  1. Créez un contrôleur qui gère les appels entrants, puis restitue les fichiers en lisant à partir d'un dossier situé en dehors d'une application (en dehors de wwwroot).

  2. Si votre projet est volumineux et que les performances sont primordiales, configurez un serveur Web petit et rapide distinct pour la diffusion de contenu statique. Ainsi, vous laisserez à IIS son travail spécifique. Cela pourrait être sur la même machine (mangouste pour Windows) ou une autre machine (nginx pour Linux). La bonne nouvelle est que vous n'avez pas à payer de licence Microsoft supplémentaire pour configurer un serveur de contenu statique sur Linux.

J'espère que cela t'aides.

1
Roman

Comme mentionné ci-dessus, la solution "acceptée" échoue sur les points d'analyse - mais les gens la marquent toujours (???). Il existe une solution beaucoup plus courte qui réplique correctement la fonctionnalité:

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

Je sais, ne gère pas les cas d'autorisations mentionnés plus tard, mais pour toutes fins utiles, FAR BETTER fournit le fonctionnalité attendue du répertoire/stock original. Supprimez () - et avec beaucoup moins de code aussi .

Vous pouvez poursuivre le traitement en toute sécurité car l'ancien répertoire sera hors de portée ... même s'il n'est pas parti car le système de fichiers est toujours en train de rattraper son retard (ou n'importe quelle excuse donnée par MS pour fournir une fonction cassée) .

Comme avantage, si vous savez que votre répertoire cible est volumineux/profond et que vous ne voulez pas attendre (ni vous déranger avec des exceptions), la dernière ligne peut être remplacée par:

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

Vous êtes toujours en sécurité pour continuer à travailler.

1
Rob

Je pense qu'il y a un fichier ouvert par un flux dont vous n'êtes pas au courant. J'ai le même problème et je l'ai résolu en fermant tous les flux pointant vers le répertoire que je voulais supprimer.

0
Ahmad Moussa

Cette erreur se produit si un fichier ou un répertoire est considéré comme étant en cours d'utilisation. C'est une erreur trompeuse. Vérifiez si des fenêtres de l'Explorateur ou des fenêtres de ligne de commande sont ouvertes sur un répertoire de l'arborescence ou sur un programme utilisant un fichier de cette arborescence.

0
allen1

dans le cas de fichiers réseau, Directory.DeleteHelper (récursif: = true) peut provoquer une exception IOException, provoquée par le retard de la suppression du fichier.

0
crowdy

Si le répertoire actuel de votre application (ou de toute autre application) est celui que vous essayez de supprimer, il ne s'agira pas d'une erreur de violation d'accès, mais aucun répertoire ne sera vide. Assurez-vous qu'il ne s'agit pas de votre propre application en modifiant le répertoire actuel. Assurez-vous également que le répertoire n’est pas ouvert dans un autre programme (Word, Excel, Total Commander, etc.). La plupart des programmes seront cd dans le répertoire du dernier fichier ouvert, ce qui provoquerait cela.

0
configurator

J'ai résolu un cas possible du problème indiqué lorsque les méthodes étaient asynchrones et codées comme suit:

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

Avec ça:

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

Conclusion? Il existe un aspect asynchrone de la suppression du descripteur utilisé pour vérifier l'existence à laquelle Microsoft n'a pas été en mesure de parler. C'est comme si la méthode asynchrone à l'intérieur d'une instruction if avait l'instruction if agissant comme une instruction using.

0
Pat Pattillo

J'ai résolu avec cette technique millénaire (vous pouvez laisser le fil. Dormez seul dans la prise)

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
Mauricio Rdz