Impossibile eliminare la directory con Directory.Delete (percorso, vero)


383

Sto usando .NET 3.5, cercando di eliminare ricorsivamente una directory usando:

Directory.Delete(myPath, true);

La mia comprensione è che questo dovrebbe essere lanciato se i file sono in uso o c'è un problema di permessi, ma altrimenti dovrebbe eliminare la directory e tutto il suo contenuto.

Tuttavia, di tanto in tanto ottengo questo:

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

Non sono sorpreso che il metodo a volte passi, ma sono sorpreso di ricevere questo particolare messaggio quando ricorsivo è vero. ( So che la directory non è vuota.)

C'è un motivo per cui vedrei questo invece di AccessViolationException?


13
Non vedresti AccessViolationException - questo è per operazioni di puntatore non valide, non per l'accesso al disco.
Joe White,

1
Questo sembra essere una sorta di problema di I / O diverso dalla semplice directory non vuota, come gli handle di file aperti o qualcosa del genere. Proverei a utilizzare l'opzione di eliminazione ricorsiva, quindi in una cattura di IOException, cerco e chiudo eventuali handle di file aperti, quindi riprovo. C'è una discussione su che qui: stackoverflow.com/questions/177146/...
Dan Csharpster

Risposte:


231

Nota del redattore: sebbene questa risposta contenga alcune informazioni utili, in realtà non è corretta sul funzionamento di Directory.Delete. Si prega di leggere i commenti per questa risposta e altre risposte a questa domanda.


Mi sono imbattuto in questo problema prima.

La radice del problema è che questa funzione non elimina i file che si trovano all'interno della struttura della directory. Quindi quello che devi fare è creare una funzione che elimini tutti i file all'interno della struttura della directory, quindi tutte le directory prima di rimuovere la directory stessa. So che questo va contro il secondo parametro ma è un approccio molto più sicuro. Inoltre, probabilmente vorrai rimuovere gli attributi di accesso SOLO LETTURA dai file subito prima di eliminarli. Altrimenti ciò solleverà un'eccezione.

Basta schiaffeggiare questo codice nel tuo progetto.

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

Inoltre, per me aggiungo personalmente una restrizione sulle aree della macchina che possono essere cancellate perché vuoi che qualcuno chiami questa funzione C:\WINDOWS (%WinDir%)o C:\.


117
Questo non ha senso. Directory.Delete (myPath, true) è un sovraccarico che elimina tutti i file all'interno della struttura di directory. Se vuoi sbagliarti, sbagli con la risposta di Ryan S.
Sig. Tolleranza,

35
+1 perché sebbene Directory.Delete () elimini i file all'interno delle sue sottodirectory (con recursive = true), genera una "IOException: la directory non è vuota" se una delle sottodirectory o dei file è di sola lettura. Quindi questa soluzione funziona meglio di Directory.Delete ()
Anthony Brien,

17
L'affermazione che Directory.Delete(path, true)non elimina i file è errata. Vedi MSDN msdn.microsoft.com/en-us/library/fxeahc5f.aspx
Konstantin Spirin

20
-1 Qualcuno può mettere per favore un chiaro indicatore che la validità di questo approccio è molto in dubbio. Se Directory.Delete(string,bool)fallisce, qualcosa è bloccato o erroneamente accettato e non esiste una dimensione adatta a qualsiasi problema. Le persone hanno bisogno di affrontare questo problema nel loro contesto e non dovremmo far crescere un grande lancio peloso di ogni idea sul problema (con tentativi e deglutizione delle eccezioni) e sperando in un buon risultato.
Ruben Bartelink,

37
Fai attenzione a questo approccio se la tua directory che hai eliminato ha scorciatoie / collegamenti simbolici ad altre cartelle - potresti finire per cancellare più di quanto ti aspettassi
Chanakya,

182

Se si sta tentando di eliminare ricorsivamente la directory ae la directory a\bè aperta in Explorer, bverrà eliminata ma verrà visualizzato l'errore "directory non vuota" aanche se è vuota quando si va a cercare. La directory corrente di qualsiasi applicazione (incluso Explorer) mantiene un handle per la directory . Quando chiami Directory.Delete(true), cancella dal basso verso l'alto:, bquindi a. Se bè aperto in Explorer, Explorer rileverà la cancellazione di b, cambierà directory verso l'alto cd ..e ripulirà gli handle aperti. Poiché il file system funziona in modo asincrono, l' Directory.Deleteoperazione non riesce a causa di conflitti con Explorer.

Soluzione incompleta

Inizialmente ho pubblicato la seguente soluzione, con l'idea di interrompere il thread corrente per consentire a Explorer di rilasciare l'handle della directory.

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

Questo funziona solo se la directory aperta è il figlio immediato della directory che si sta eliminando. Se a\b\c\dè aperto in Explorer e lo usi su a, questa tecnica fallirà dopo l'eliminazione de c.

Una soluzione leggermente migliore

Questo metodo gestirà la cancellazione di una struttura di directory profonda anche se una delle directory di livello inferiore è aperta in 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);
    }
}

Nonostante il lavoro extra di ricorrere da soli, dobbiamo ancora gestire UnauthorizedAccessExceptionciò che può accadere lungo la strada. Non è chiaro se il primo tentativo di eliminazione stia aprendo la strada per il secondo, riuscito, o se è semplicemente il ritardo temporale introdotto dal lancio / cattura di un'eccezione che consente al file system di recuperare.

Potresti essere in grado di ridurre il numero di eccezioni generate e rilevate in condizioni tipiche aggiungendo Thread.Sleep(0)a all'inizio del tryblocco. Inoltre, sussiste il rischio che, sotto un carico pesante del sistema, si possano volare entrambi i Directory.Deletetentativi e fallire. Considera questa soluzione un punto di partenza per una più robusta cancellazione ricorsiva.

Risposta generale

Questa soluzione risolve solo le peculiarità dell'interazione con Windows Explorer. Se si desidera un'operazione di eliminazione solida, una cosa da tenere a mente è che qualsiasi cosa (scanner antivirus, qualunque cosa) potrebbe avere un controllo aperto su ciò che si sta tentando di eliminare, in qualsiasi momento. Quindi devi riprovare più tardi. Quanto più tardi e quante volte si tenta, dipende da quanto sia importante che l'oggetto venga eliminato. Come indica MSDN ,

Il robusto codice di iterazione dei file deve tenere conto di molte complessità del file system.

Questa dichiarazione innocente, fornita solo con un link alla documentazione di riferimento NTFS, dovrebbe farti alzare i capelli.

( Modifica : molto. Questa risposta originariamente aveva solo la prima soluzione incompleta.)


11
Sembra chiamare Directory.Delete (path, true) mentre path o una delle cartelle / file sotto path è aperta o selezionata in Esplora risorse genererà una IOException. Chiudere Esplora risorse e rieseguire il mio codice esistente senza il metodo try / catch suggerito sopra funzionava bene.
David Alpert,

1
Non riesco a capire come e perché funzioni, ma ha funzionato per me durante l'impostazione degli attributi del file e la scrittura della mia funzione ricorsiva no.
Stilgar,

1
@CarlosLiu Perché sta dando a "Explorer la possibilità di rilasciare l'handle della directory"
Dmitry Gonchar

4
Ciò che sta accadendo è che il sistema chiede a Explorer di "rilasciare l'handle della directory", quindi tenta di eliminare la directory. Se l'handle della directory non è stato eliminato in tempo, viene sollevata un'eccezione e il catchblocco viene eseguito (nel frattempo Explorer sta ancora rilasciando la directory, poiché non è stato inviato alcun comando per dirgli di non farlo). La chiamata a Thread.Sleep(0)può essere o non essere necessaria, in quanto il catchblocco ha già concesso al sistema un po 'più di tempo, ma fornisce un po' di sicurezza in più a basso costo. Dopodiché, Deleteviene chiamato, con la directory già rilasciata.
Zachary Kniebel,

1
@PandaWood in realtà solo questo Sleep (100) ha funzionato per me. La sospensione (0) non ha funzionato. Non ho idea di cosa stia succedendo e come risolverlo correttamente. Voglio dire, cosa succede se dipende dal carico del server e in futuro ci dovrebbero essere 300 o 400? Come saperlo. Deve essere un altro modo corretto ...
Roman

43

Prima di andare oltre, controlla per i seguenti motivi che sono sotto il tuo controllo:

  • La cartella è impostata come directory corrente del processo? Se sì, prima cambiarlo in qualcos'altro
  • Hai aperto un file (o caricato una DLL) da quella cartella? (e ho dimenticato di chiuderlo / scaricarlo)

Altrimenti, controlla per i seguenti motivi legittimi al di fuori del tuo controllo:

  • Ci sono file contrassegnati come di sola lettura in quella cartella.
  • Non disponi dell'autorizzazione per l'eliminazione di alcuni di questi file.
  • Il file o la sottocartella sono aperti in Esplora risorse o in un'altra app.

Se uno di questi problemi è il problema, dovresti capire perché ciò accade prima di provare a migliorare il codice di eliminazione. La tua app dovrebbe eliminare i file di sola lettura o inaccessibili? Chi li ha contrassegnati in questo modo e perché?

Una volta che hai escluso i motivi di cui sopra, c'è ancora la possibilità di fallimenti spuri. L'eliminazione avrà esito negativo se qualcuno detiene un handle per uno qualsiasi dei file o cartelle che vengono eliminati e ci sono molti motivi per cui qualcuno potrebbe enumerare la cartella o leggere i suoi file:

  • cerca indicizzatori
  • anti-virus
  • software di backup

L'approccio generale per affrontare fallimenti spuri è quello di provare più volte, facendo una pausa tra i tentativi. Ovviamente non vuoi continuare a provare per sempre, quindi dovresti rinunciare dopo un certo numero di tentativi e lanciare un'eccezione o ignorare l'errore. Come questo:

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
}

Secondo me, un aiuto del genere dovrebbe essere usato per tutte le cancellazioni perché sono sempre possibili fallimenti spuri. Tuttavia, DEVI ADATTARE QUESTO CODICE NEL CASO DI UTILIZZO, non solo copiarlo alla cieca.

Ho avuto errori spuri per una cartella di dati interna generata dalla mia app, situata in% LocalAppData%, quindi la mia analisi è la seguente:

  1. La cartella è controllata esclusivamente dalla mia applicazione e l'utente non ha alcun motivo valido per andare a contrassegnare le cose come di sola lettura o inaccessibili all'interno di quella cartella, quindi non provo a gestire quel caso.

  2. Non ci sono contenuti preziosi creati dagli utenti, quindi non c'è il rischio di eliminare forzatamente qualcosa per errore.

  3. Essendo una cartella di dati interna, non mi aspetto che sia aperto in Explorer, almeno non sento la necessità di gestire il caso in modo specifico (cioè sto gestendo bene quel caso tramite supporto).

  4. Se tutti i tentativi falliscono, scelgo di ignorare l'errore. Nel peggiore dei casi, l'app non riesce a decomprimere alcune risorse più recenti, si arresta in modo anomalo e richiede all'utente di contattare l'assistenza, il che è accettabile per me a condizione che non accada spesso. Oppure, se l'app non si arresta in modo anomalo, lascerà indietro alcuni vecchi dati, il che è di nuovo accettabile per me.

  5. Ho scelto di limitare i tentativi a 500ms (50 * 10). Questa è una soglia arbitraria che funziona nella pratica; Volevo che la soglia fosse abbastanza breve in modo che gli utenti non uccidessero l'app, pensando che avesse smesso di rispondere. D'altra parte, mezzo secondo è un sacco di tempo perché l'autore del reato finisca di elaborare la mia cartella. A giudicare da altre risposte SO che a volte trovano addirittura Sleep(0)accettabile, pochissimi utenti sperimenteranno mai più di un singolo tentativo.

  6. Riprovo ogni 50 ms, che è un altro numero arbitrario. Sento che se un file viene elaborato (indicizzato, controllato) quando provo a eliminarlo, 50ms è il momento giusto per aspettarmi che l'elaborazione venga completata nel mio caso. Inoltre, 50ms è abbastanza piccolo da non provocare un notevole rallentamento; di nuovo, Sleep(0)sembra essere abbastanza in molti casi, quindi non vogliamo ritardare troppo.

  7. Il codice riprova su eventuali eccezioni di I / O. Normalmente non mi aspetto eccezioni nell'accesso a% LocalAppData%, quindi ho scelto la semplicità e ho accettato il rischio di un ritardo di 500 ms nel caso in cui si verifichi un'eccezione legittima. Inoltre, non volevo trovare un modo per rilevare l'esatta eccezione su cui volevo riprovare.


7
PPS Qualche mese dopo, sono felice di segnalare che questo (un po 'folle) codice ha risolto completamente il problema. Le richieste di supporto per questo problema sono fino a zero (da circa 1-2 a settimana).
Andrey Tarantsov,

1
+0 Mentre questo è più robusto e meno 'eccolo qui; la soluzione perfetta per te "di stackoverflow.com/a/7518831/11635 , per me vale lo stesso - programmazione per coincidenza - gestirla con cura. Un punto utile incarnato nel tuo codice è che se stai per fare un nuovo tentativo, devi considerare che sei in una gara con l'ambiguità del fatto che la Directory abbia "Andato" dall'ultimo tentativo [e una Directory.Existsguardia di sicurezza avrebbe non risolverlo.]
Ruben Bartelink,

1
lo adoro ... non so cosa sto facendo che questo è sempre un punto dolente per me ... ma non è perché ho la directory aperta in Explorer ... non molto clamore su Internet per questo di più o meno bug ... almeno io e Andrey abbiamo un modo per affrontarlo :)
TCC

2
@RubenBartelink OK, quindi penso che possiamo essere d'accordo su questo: pubblicare un pezzo di codice che funzioni per un'app specifica (e non è mai stato pensato per essere adatto a tutti i casi) poiché una risposta SO sarà un disservizio per molti principianti e / o sviluppatori ignoranti. L'ho dato come punto di partenza per la personalizzazione, ma sì, alcune persone lo useranno così com'è, e questa è una brutta cosa.
Andrey Tarantsov,

2
@nopara Non è necessario il confronto; se siamo fuori dal giro, abbiamo fallito. E sì, in molti casi vorrai lanciare un'eccezione, quindi aggiungere lo stack di gestione degli errori appropriato nello stack, probabilmente con un messaggio visibile all'utente.
Andrey Tarantsov,

18

Risposta asincrona moderna

La risposta accettata è semplicemente sbagliata, potrebbe funzionare per alcune persone perché il tempo impiegato per ottenere i file dal disco libera qualsiasi cosa stesse bloccando i file. Il fatto è che ciò accade perché i file vengono bloccati da altri processi / stream / azioni. Le altre risposte usano Thread.Sleep(Yuck) per riprovare a cancellare la directory dopo qualche tempo. Questa domanda deve essere rivisitata con una risposta più moderna.

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

Test unitari

Questi test mostrano un esempio di come un file bloccato può causare l' Directory.Deleteerrore e come il TryDeleteDirectorymetodo sopra risolve il problema.

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

Puoi ampliare ciò che intendi per "moderno"? Quali sono i vantaggi del tuo approccio? Perché gli altri, secondo te, sono sbagliati?
TinyRacoon,

1
Altri non hanno torto. Usano solo API più vecchie come quelle Thread.Sleepche dovresti evitare oggi e usano invece async/ awaitcon Task.Delay. È comprensibile, questa è una domanda molto antica.
Muhammad Rehan Saeed,

Questo approccio non funzionerà in VB.Net (almeno non con una conversione riga per riga molto letterale) a causa diBC36943 'Await' cannot be used inside a 'Catch' statement, a 'Finally' statement, or a 'SyncLock' statement.
amonroejj

@amonroejj Devi utilizzare una versione precedente. È stato risolto.
Muhammad Rehan Saeed,

Poco miglioramento invece di restituire true if (!Directory.Exists(directoryPath)) { return true; } await Task.Delay(millisecondsDelay); per attendere fino a quando la directory non è davvero andata
fuchs777,

16

Una cosa importante che dovrebbe essere menzionata (l'ho aggiunto come commento ma non mi è permesso) è che il comportamento del sovraccarico è cambiato da .NET 3.5 a .NET 4.0.

Directory.Delete(myPath, true);

A partire da .NET 4.0 elimina i file nella cartella stessa ma NON in 3.5. Questo può essere visto anche nella documentazione MSDN.

.NET 4.0

Elimina la directory specificata e, se indicato, eventuali sottodirectory e file nella directory.

.NET 3.5

Elimina una directory vuota e, se indicato, eventuali sottodirectory e file nella directory.


3
Penso che sia solo una modifica della documentazione ... se cancella solo una "directory vuota", cosa significherebbe eliminare anche i file nella directory, con il 2 ° parametro? Se è vuoto non ci sono file ...
Pisu,

Temo che tu stia assumendo qualcosa di sbagliato. Ho pubblicato questo dopo aver testato il codice con entrambe le versioni del framework. L'eliminazione di una cartella non vuota in 3.5 genererà un'eccezione.
jettatore,

15

Ho avuto lo stesso problema con Delphi. E il risultato finale è stato che la mia applicazione stava bloccando la directory che volevo eliminare. In qualche modo la directory è stata bloccata mentre scrivevo (alcuni file temporanei).

Il problema era che ho fatto una semplice directory di modifica al suo genitore prima di eliminarlo.


6
+1 Ora c'è qualcosa che msdn per Directory.Delete menziona!
Ruben Bartelink,

3
qualche soluzione finale con un esempio di codice sorgente completo che ci sta lavorando?
Kiquenet,

11

Sono sorpreso che nessuno abbia pensato a questo semplice metodo non ricorsivo, che può eliminare directory contenenti file di sola lettura, senza la necessità di modificare l'attributo di sola lettura di ciascuno di essi.

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

(Cambia un po 'per non sparare momentaneamente una finestra cmd, che è disponibile su Internet)


Bello da condividere con noi, ma saresti così gentile da includere il bit di modifica necessario per evitare di attivare la finestra cmd, invece di spingerci a cercarlo sulla rete?
ThunderGr,

Questo non funziona Nella stessa situazione in cui posso cancellare il file da un prompt dei comandi o da Explorer, usando questo codice per chiamare rmdir si ottiene il codice di uscita 145 che si traduce in "La directory non è vuota". Lascia la directory vuota ma anche al suo posto, esattamente come Directory.Delete ("", vero)
Kevin Coulombe,

@Kevin Coulombe, Humm ... Sei sicuro di utilizzare le opzioni / s / q?
Piyush Soni,

1
@KevinCoulombe: Sì, devono essere quei componenti COM. Quando provo attraverso il semplice vecchio C #, funziona e cancella la directory insieme ai file all'interno (solo lettura o non sola lettura).
Piyush Soni,

5
Se inizi a fare affidamento su componenti esterni per quello che dovrebbe essere nel framework, allora è un'idea "meno che ideale" perché non è più portatile (o più difficile). E se l'exe non ci fosse? O l'opzione / è cambiata? Se la soluzione di Jeremy Edwards funziona, allora dovrebbe essere preferito IMHO
frenchone

11

È possibile riprodurre l'errore eseguendo:

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

Quando si tenta di eliminare la directory 'b', genera IOException "La directory non è vuota". È stupido da quando abbiamo appena cancellato la directory 'c'.

Da quanto ho capito, la spiegazione è che la directory 'c' è contrassegnata come cancellata. Ma la cancellazione non è ancora stata eseguita nel sistema. Il sistema ha risposto che il lavoro è stato completato, mentre in realtà è ancora in fase di elaborazione. Probabilmente il sistema attende che Esplora file si concentri sulla directory principale per eseguire il commit dell'eliminazione.

Se guardi il codice sorgente della funzione Elimina ( http://referencesource.microsoft.com/#mscorlib/system/io/directory.cs ) vedrai che utilizza la funzione nativa Win32Native.RemoveDirectory. Questo comportamento non aspettare è annotato qui:

La funzione RemoveDirectory contrassegna una directory per l'eliminazione alla chiusura. Pertanto, la directory non viene rimossa fino alla chiusura dell'ultimo handle della directory.

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

Dormire e riprovare è la soluzione. Vedi la soluzione di ryascl.


8

Ho avuto quegli strani problemi di autorizzazione durante l'eliminazione delle directory dei profili utente (in C: \ Documents and Settings) nonostante potessi farlo nella shell di Explorer.

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

Non ha senso per me un'operazione di "file" su una directory, ma so che funziona e questo è abbastanza per me!


2
Ancora nessuna speranza, quando la directory ha molti file ed Explorer sta aprendo la cartella contenente quei file.
vede il

3

Questa risposta si basa su: https://stackoverflow.com/a/1703799/184528 . La differenza con il mio codice è che quando si richiama una sottodirectory e si eliminano molti sottodirectory e file, quando necessario una chiamata a Directory non viene eseguita.

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

Quindi, come dovrebbe eliminare la cartella se ci fosse un UnauthorizedAccessException? Sarebbe solo buttare, di nuovo. E di nuovo. E ancora ... Perché ogni volta andrà alla catche richiamerà di nuovo la funzione. A Thread.Sleep(0);non modifica le tue autorizzazioni. Dovrebbe solo registrare l'errore e fallire con grazia, a quel punto. E questo ciclo continuerà fino a quando la (sotto-) directory è aperta - non la chiude a livello di programmazione. Siamo pronti a lasciarlo fare solo finché quelle cose restano aperte? Esiste un modo migliore?
Vapcguy,

Se esiste UnauthorizedAccessException, proverà a eliminare manualmente ogni file manualmente. Quindi continua a fare progressi attraversando la struttura delle directory. Sì, potenzialmente ogni file e directory getteranno la stessa eccezione, ma questo può anche accadere semplicemente perché esploratore sta tenendo una maniglia ad esso (vedi stackoverflow.com/a/1703799/184528 ) cambierò il "tryagain" a "secondTry" per renderlo più chiaro.
cdiggins,

Per rispondere in modo più succinto, passa "vero" ed esegue un percorso di codice diverso.
cdiggins,

Bene, ho visto la tua modifica, ma il mio punto non è con la cancellazione dei file, ma con la cancellazione della directory. Ho scritto un codice in cui potevo fare essenzialmente Process.Kill()su qualsiasi processo in cui un file può essere bloccato ed eliminare i file. Il problema che ho riscontrato è quando si elimina una directory in cui uno di quei file era ancora aperto (consultare stackoverflow.com/questions/41841590/… ). Quindi tornando indietro attraverso questo ciclo, non importa cos'altro stia facendo, se lo fa di nuovo Directory.Delete()su quella cartella, fallirà comunque se quell'handle non può essere rilasciato.
Vapcguy,

E lo stesso accadrebbe per un UnauthorizedAccessExceptiondato che l'eliminazione dei file (supponendo che fosse persino consentito, perché per arrivare a quel codice, non è riuscito Directory.Delete()) non ti dà magicamente il permesso di eliminare la directory.
Vapcguy,

3

Non delle soluzioni precedenti ha funzionato bene per me. Ho finito usando una versione modificata della soluzione @ryascl come di seguito:

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

È possibile che tu abbia una race condition in cui un altro thread o processo sta aggiungendo file alla directory:

La sequenza sarebbe:

Deleter process A:

  1. Svuota la directory
  2. Elimina la directory (ora vuota).

Se qualcun altro aggiunge un file tra 1 e 2, forse 2 genererebbe l'eccezione elencata?


2

Ho trascorso alcune ore a risolvere questo problema e altre eccezioni con l'eliminazione della directory. Questa è la mia soluzione

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

Questo codice ha un piccolo ritardo, che non è importante per la mia applicazione. Ma fai attenzione, il ritardo potrebbe essere un problema per te se hai molte sottodirectory all'interno della directory che desideri eliminare.


8
-1 Qual è il ritardo? Nessuna programmazione per coincidenza, per favore!
Ruben Bartelink,

1
@ Ruben Non ho detto che ti sbagli. Ho appena detto che ridimensionarlo solo per questo è una dura punizione. Sono d'accordo con te, tuttavia, i 4 voti positivi non hanno portato a 4 voti negativi. Vorrei anche votare il tuo commento, ma non vorrei sottovalutare la risposta a causa di un ritardo inspiegabile :)
ThunderGr

1
@RubenBartelink e altri: anche se questo codice non mi piace in modo specifico (ho pubblicato un'altra soluzione con un approccio simile), il ritardo qui è ragionevole. Molto probabilmente il problema è al di fuori del controllo dell'app; forse un'altra app salva periodicamente l'FS, bloccando così la cartella per brevi periodi di tempo. Il ritardo risolve il problema, portando il conto alla rovescia a zero. Chi se ne frega se non abbiamo la più pallida idea della causa principale?
Andrey Tarantsov,

1
@RubenBartelink In effetti, quando ci pensi, non usare un approccio di ritardo e riprovare durante la cancellazione della directory NTFS è una soluzione irresponsabile qui. Qualsiasi tipo di attraversamento di file in corso blocca l'eliminazione, quindi prima o poi fallirà. E non puoi aspettarti che tutti gli strumenti di ricerca, backup, antivirus e gestione dei file di terze parti rimangano fuori dalla tua cartella.
Andrey Tarantsov,

1
@RubenBartelink Un altro esempio, supponiamo che si dia un ritardo di 100 ms e che il tempo di blocco più alto di qualsiasi software sul PC di destinazione sia il software AV = 90 ms. Supponiamo che abbia anche un software di backup che blocca i file per 70ms. Ora l'AV blocca un file, la tua app attende 100 ms, il che va normalmente bene, ma poi incontra un altro blocco perché il software di backup inizia a catturare il file nel segno di 70 ms della scansione AV, e quindi ci vorranno altri 40ms per rilasciare il file. Quindi, mentre il software AV impiega più tempo e i 100 ms sono normalmente più lunghi di una delle 2 app, devi comunque tener conto di quando inizia nel mezzo.
vapcguy,

2

L'eliminazione ricorsiva della directory che non elimina i file è certamente inaspettata. La mia soluzione per questo:

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

Ho riscontrato casi in cui ciò ha aiutato, ma in generale, Directory.Delete a eliminare i file all'interno delle directory dopo l'eliminazione ricorsiva, come documentato in msdn .

Di tanto in tanto riscontro questo comportamento irregolare anche come utente di Windows Explorer: a volte non riesco a eliminare una cartella (penso che il messaggio senza senso sia "accesso negato") ma quando eseguo il drill down e elimino gli elementi inferiori posso quindi eliminare quello superiore anche articoli. Quindi immagino che il codice sopra riportato riguardi un'anomalia del sistema operativo, non un problema di libreria di classe base.


1

La directory o un file al suo interno è bloccato e non può essere eliminato. Trova il colpevole che lo blocca e vedi se riesci a eliminarlo.


Da T1000 a user-with-folder-open: "Sei terminato!"
vapcguy,

1

Sembra che avere il percorso o la sottocartella selezionati in Esplora risorse sia sufficiente per bloccare una singola esecuzione di Directory.Delete (percorso, vero), lanciando una IOException come descritto sopra e morendo invece di avviare Windows Explorer in una cartella principale e procedendo come previsto.


Questo sembra essere stato il mio problema. Non appena ho chiuso Explorer ed eseguito di nuovo, nessuna eccezione. Anche selezionare il genitore del genitore non era abbastanza. Ho dovuto chiudere davvero Explorer.
Scott Marlowe,

Sì, questo succede ed è una causa. Qualche idea su come gestirlo a livello di codice o la risposta è sempre solo per assicurarsi che tutti i 1000 utenti abbiano chiuso quella cartella?
vapcguy,

1

Ho avuto questo problema oggi. Stava succedendo perché avevo Windows Explorer aperto nella directory che cercava di essere eliminato, causando il fallimento della chiamata ricorsiva e quindi l'IOException. Assicurarsi che non vi siano maniglie aperte nella directory.

Inoltre, MSDN è chiaro che non è necessario scrivere la propria recusione: http://msdn.microsoft.com/en-us/library/fxeahc5f.aspx


1

Ho avuto lo stesso problema con Windows Workflow Foundation su un server di build con TFS2012. Internamente, il flusso di lavoro chiamato Directory.Delete () con il flag ricorsivo impostato su true. Sembra essere legato alla rete nel nostro caso.

Stavamo eliminando una cartella di rilascio binaria su una condivisione di rete prima di ricrearla e ripopolarla con gli ultimi binari. Ogni altra build fallirebbe. Quando si apriva la cartella di rilascio dopo una compilazione non riuscita, la cartella era vuota, il che indica che ogni aspetto della chiamata Directory.Delete () ha avuto esito positivo tranne che per l'eliminazione della directory effettiva.

Il problema sembra essere causato dalla natura asincrona delle comunicazioni dei file di rete. Il server di compilazione ha detto al file server di eliminare tutti i file e il file server ha riferito di averlo fatto, anche se non era completamente finito. Quindi il server di compilazione ha richiesto l'eliminazione della directory e il file server ha rifiutato la richiesta perché non aveva completato completamente l'eliminazione dei file.

Due possibili soluzioni nel nostro caso:

  • Costruisci la cancellazione ricorsiva nel nostro codice con ritardi e verifiche tra ogni passaggio
  • Riprova fino a X volte dopo una IOException, dando un ritardo prima di riprovare

Quest'ultimo metodo è veloce e sporco ma sembra fare il trucco.


1

Ciò è dovuto a FileChangesNotifications.

Succede da ASP.NET 2.0. Quando elimini una cartella all'interno di un'app, questa viene riavviata . Puoi vederlo tu stesso, usando ASP.NET Health Monitoring .

Aggiungi questo codice al tuo web.config / configuration / system.web:

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


Dopo quel check out Windows Log -> Application. Cosa sta succedendo:

Quando si elimina una cartella, se è presente una sottocartella, Delete(path, true)elimina prima la sottocartella. È sufficiente che FileChangesMonitor sia a conoscenza della rimozione e della chiusura dell'app. Nel frattempo la tua directory principale non è stata ancora cancellata. Questo è l'evento dal registro:


inserisci qui la descrizione dell'immagine


Delete() non ha terminato il suo lavoro e poiché l'app si sta chiudendo, genera un'eccezione:

inserisci qui la descrizione dell'immagine

Quando non hai sottocartelle in una cartella che stai eliminando, Elimina () elimina solo tutti i file e quella cartella, anche l'app viene riavviata, ma non ottieni eccezioni , poiché il riavvio dell'app non interrompe nulla. Tuttavia, perdi tutte le sessioni in-process, l'app non risponde alle richieste al riavvio, ecc.

E adesso?

Esistono alcune soluzioni alternative e modifiche per disabilitare questo comportamento, Directory Junction , Disattivazione di FCN con il Registro di sistema , Arresto di FileChangesMonitor utilizzando Reflection (poiché non esiste un metodo esposto) , ma tutti non sembrano essere corretti, perché FCN è disponibile per un Motivo. Si occupa della struttura della tua app , che non è la struttura dei tuoi dati . La risposta breve è: posizionare le cartelle che si desidera eliminare al di fuori della propria app. FileChangesMonitor non riceverà notifiche e l'app non verrà riavviata ogni volta. Non otterrai eccezioni. Per renderli visibili dal web ci sono due modi:

  1. Crea un controller che gestisca le chiamate in arrivo e poi rispedisca i file leggendo dalla cartella all'esterno di un'app (all'esterno di wwwroot).

  2. Se il tuo progetto è grande e le prestazioni sono le più importanti, imposta un server web separato piccolo e veloce per servire contenuti statici. In questo modo lascerai a IIS il suo lavoro specifico. Potrebbe essere sulla stessa macchina (mangusta per Windows) o su un'altra macchina (nginx per Linux). La buona notizia è che non è necessario pagare una licenza Microsoft aggiuntiva per configurare un server di contenuti statici su Linux.

Spero che sia di aiuto.


1

Come accennato in precedenza, la soluzione "accettata" non riesce sui punti di analisi - eppure la gente lo contrassegna ancora (???). C'è una soluzione molto più breve che replica correttamente la funzionalità:

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

Lo so, non gestisce i casi di autorizzazione menzionati più avanti, ma a tutti gli effetti FAR BETTER fornisce le funzionalità previste da Directory.Delete () originale / stock - e con molto meno codice .

Puoi continuare a lavorare in modo sicuro perché la vecchia directory sarà fuori mano ... anche se non sparita perché il "file system sta ancora recuperando" (o qualunque scusa abbia dato MS per fornire una funzione non funzionante) .

Come vantaggio, se sai che la tua directory di destinazione è grande / profonda e non vuoi aspettare (o preoccuparti delle eccezioni), l'ultima riga può essere sostituita con:

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

Sei ancora sicuro di continuare a lavorare.


2
La tua assegnazione può essere semplificata da: string tfilename = Path.Combine (Path.GetDirectoryName (target), Path.GetRandomFileName ());
Pete,

1
Sono d'accordo con Pete. Il codice scritto non aggiungerà il separatore. Ha preso la mia strada \\server\C$\dire ce l'ha fatta \\server\C$asf.yuw. Di conseguenza ho ricevuto un errore su Directory.Move()- Source and destination path must have identical roots. Move will not work across volumes. Ha funzionato bene una volta che ho usato il codice di Pete EXCEPT né gestisce per quando ci sono file bloccati o directory aperte, quindi non arriva mai al ThreadPoolcomando.
vapcguy,

ATTENZIONE: questa risposta deve essere utilizzata solo con recursive = true. Se falso, questo sposterà la directory anche se non è vuota. Quale sarebbe un bug; il comportamento corretto in quel caso è quello di lanciare un'eccezione e lasciare la directory com'era.
ToolmakerSteve

1

Questo problema può apparire su Windows quando ci sono file in una directory (o in qualsiasi sottodirectory) la cui lunghezza del percorso è maggiore di 260 simboli.

In tali casi è necessario eliminare \\\\?\C:\mydiranziché C:\mydir. Informazioni sul limite di 260 simboli che puoi leggere qui .


1

Ho risolto con questa tecnica millenaria (puoi lasciare il Thread. Dormi da solo nella presa)

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

Se la directory corrente dell'applicazione (o di qualsiasi altra applicazione) è quella che stai tentando di eliminare, non si verificherà un errore di violazione dell'accesso ma una directory non è vuota. Assicurati che non sia la tua applicazione cambiando la directory corrente; inoltre, assicurati che la directory non sia aperta in qualche altro programma (es. Word, Excel, Total Commander, ecc.). La maggior parte dei programmi eseguirà il cd nella directory dell'ultimo file aperto, il che causerebbe ciò.


0

nel caso di file di rete, Directory.DeleteHelper (ricorsivo: = true) potrebbe causare IOException causata dal ritardo nell'eliminazione del file


0

Penso che ci sia un file aperto da qualche stream di cui non sei a conoscenza ho avuto lo stesso problema e l'ho risolto chiudendo tutti i flussi che puntavano alla directory che volevo eliminare.


0

Questo errore si verifica se qualsiasi file o directory viene considerato in uso. È un errore fuorviante. Verificare se sono presenti finestre di Esplora risorse o finestre della riga di comando aperte su una directory della struttura o su un programma che utilizza un file nella struttura.


0

Ho risolto una possibile istanza del problema dichiarato quando i metodi erano asincroni e codificati in questo modo:

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

Con questo:

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

Conclusione? Esistono alcuni aspetti asincroni dell'eliminazione dell'handle utilizzato per verificare l'esistenza con cui Microsoft non è stata in grado di parlare. È come se il metodo asincrono all'interno di un'istruzione if abbia l'istruzione if che agisce come un'istruzione using.


0

Non è necessario creare un metodo aggiuntivo per la ricorsività o eliminare i file all'interno della cartella extra. Tutto questo facendo automaticamente chiamando

DirectoryInfo.Delete ();

I dettagli sono qui .

Qualcosa del genere funziona abbastanza bene:

  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
    }

passando true come variabile per eliminare il metodo, eliminerà anche i file secondari e la cartella secondaria con i file.


-2

Nessuna delle risposte sopra ha funzionato per me. Sembra che l'utilizzo della mia app nella DirectoryInfodirectory di destinazione lo abbia bloccato.

La forzatura della garbage collection sembrava risolvere il problema, ma non subito. Alcuni tentativi di eliminazione dove richiesto.

Nota Directory.Existscome può scomparire dopo un'eccezione. Non so perché l'eliminazione per me è stata ritardata (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 Programmazione per coincidenza. Quale oggetto fa cosa quando GC'd? Questo è in qualche modo un buon consiglio generale? (Ti credo quando dici che hai avuto un problema e che hai usato questo codice e che ritieni di non avere un problema ora, ma non è questo il punto)
Ruben Bartelink,

@RubenBartelink Sono d'accordo. È un trucco. Codice Voodoo che fa qualcosa quando non è chiaro cosa sta risolvendo o come. Mi piacerebbe una soluzione adeguata.
Reactgular,

1
Il mio problema è che tutto ciò che aggiunge oltre stackoverflow.com/a/14933880/11635 è altamente speculativo. Se potessi, darei un -1 per la duplicazione e un -1 per la speculazione / programmazione per coincidenza. Spruzzare GC.Collectè a) solo un cattivo consiglio eb) non una causa generale sufficientemente comune di directory bloccate per meritare qui l'inclusione. Basta scegliere uno degli altri e non seminare più confusione nella mente di lettori innocenti
Ruben Bartelink,

3
Usa GC.WaitForPendingFinalizers (); dopo GC.Collect (); questo funzionerà come previsto.
Heiner,

Non sono sicuro, non testato, ma forse meglio sarebbe fare qualcosa con una usingdichiarazione, quindi: 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,

-3

aggiungi true nel secondo parametro.

Directory.Delete(path, true);

Rimuoverà tutto.


1
Directory.Delete (path, true); era la domanda originale
Pisu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.