No se puede eliminar el directorio con Directory.Delete (ruta, verdadero)

383

Estoy usando .NET 3.5, tratando de eliminar recursivamente un directorio usando:

Directory.Delete(myPath, true);

Tengo entendido que esto debería arrojarse si los archivos están en uso o si hay un problema de permisos, pero de lo contrario, debería eliminar el directorio y todo su contenido.

Sin embargo, ocasionalmente obtengo esto:

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

No me sorprende que el método a veces arroje, pero me sorprende recibir este mensaje en particular cuando lo recursivo es cierto. ( Sé que el directorio no está vacío).

¿Hay alguna razón por la que vería esto en lugar de AccessViolationException?

Jason Anderson
fuente
13
No vería AccessViolationException, eso es para operaciones de puntero no válidas, no para acceso a disco.
Joe White
1
Esto parece ser algún tipo de problema de E / S además de que el directorio no está vacío, como los identificadores de archivos abiertos o algo así. Intentaría usar la opción de eliminación recursiva, luego, en una captura para IOException, buscaría y cerraría cualquier identificador de archivo abierto, luego volvería a intentarlo. Hay una discusión sobre eso aquí: stackoverflow.com/questions/177146/…
Dan Csharpster

Respuestas:

231

Nota del editor: aunque esta respuesta contiene información útil, en realidad es incorrecta sobre el funcionamiento de Directory.Delete. Lea los comentarios para esta respuesta y otras respuestas a esta pregunta.


Me encontré con este problema antes.

La raíz del problema es que esta función no elimina archivos que están dentro de la estructura de directorios. Entonces, lo que tendrá que hacer es crear una función que elimine todos los archivos dentro de la estructura del directorio y luego todos los directorios antes de eliminar el directorio en sí. Sé que esto va en contra del segundo parámetro, pero es un enfoque mucho más seguro. Además, es probable que desee eliminar los atributos de acceso READ-ONLY de los archivos justo antes de eliminarlos. De lo contrario, eso generará una excepción.

Simplemente inserte este código en su proyecto.

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

Además, para mí personalmente agrego una restricción en las áreas de la máquina que se pueden eliminar porque desea que alguien llame a esta función C:\WINDOWS (%WinDir%)o C:\.

Jeremy Edwards
fuente
117
Esto no tiene sentido. Directory.Delete (myPath, true) es una sobrecarga que elimina todos los archivos que están dentro de la estructura del directorio. Si quieres equivocarte, equivocate con la respuesta de Ryan S.
Sig. Tolleranza
35
+1 porque aunque Directory.Delete () elimina archivos dentro de sus subdirectorios (con recursive = true), arroja un "IOException: el directorio no está vacío" si uno de los subdirectorios o archivos es de solo lectura. Entonces, esta solución funciona mejor que Directory.Delete ()
Anthony Brien
17
Su declaración que Directory.Delete(path, true)no elimina archivos es incorrecta. Vea MSDN msdn.microsoft.com/en-us/library/fxeahc5f.aspx
Konstantin Spirin el
20
-1 ¿Puede alguien poner un marcador claro de que la validez de este enfoque es muy dudosa? Si Directory.Delete(string,bool)falla, algo está bloqueado o mal puesto y no hay una solución única para tal problema. La gente necesita abordar ese problema en su contexto y no deberíamos estar haciendo un gran esfuerzo para cada idea (con reintentos y deglución de excepciones) y esperando un buen resultado.
Ruben Bartelink
37
Tenga cuidado con este enfoque si su directorio que está eliminando tiene accesos directos / enlaces simbólicos a otras carpetas - puede terminar eliminando más de lo que esperaba
Chanakya
182

Si está intentando eliminar el directorio de forma recursiva ay el directorio a\bestá abierto en el Explorador, bse eliminará, pero obtendrá el error 'el directorio no está vacío' a, aunque esté vacío cuando vaya y mire. El directorio actual de cualquier aplicación (incluido Explorer) conserva un identificador para el directorio . Cuando llamas Directory.Delete(true), se elimina de abajo hacia arriba:, bentonces a. Si bestá abierto en Explorer, Explorer detectará la eliminación de b, cambiará el directorio hacia arriba cd ..y limpiará los controladores abiertos. Dado que el sistema de archivos funciona de forma asíncrona, la Directory.Deleteoperación falla debido a conflictos con Explorer.

Solución incompleta

Originalmente publiqué la siguiente solución, con la idea de interrumpir el hilo actual para permitir que Explorer tenga tiempo de liberar el identificador de directorio.

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

Pero esto solo funciona si el directorio abierto es el hijo inmediato del directorio que está eliminando. Si a\b\c\destá abierto en el Explorador y lo usa a, esta técnica fallará después de eliminar dy c.

Una solución algo mejor

Este método manejará la eliminación de una estructura de directorio profunda incluso si uno de los directorios de nivel inferior está abierto en el Explorador.

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

A pesar del trabajo adicional de recurrir por nuestra cuenta, todavía tenemos que manejar lo UnauthorizedAccessExceptionque puede ocurrir en el camino. No está claro si el primer intento de eliminación está allanando el camino para el segundo, exitoso, o si es simplemente el retraso de tiempo introducido por lanzar / atrapar una excepción que permite que el sistema de archivos se ponga al día.

Es posible que pueda reducir el número de excepciones lanzadas y atrapadas en condiciones típicas agregando un Thread.Sleep(0)al comienzo del trybloque. Además, existe el riesgo de que, bajo una gran carga del sistema, pueda volar a través de ambos Directory.Deleteintentos y fallar. Considere esta solución como un punto de partida para una eliminación recursiva más sólida.

Respuesta general

Esta solución solo aborda las peculiaridades de interactuar con el Explorador de Windows. Si desea una operación de eliminación sólida, una cosa a tener en cuenta es que cualquier cosa (escáner de virus, lo que sea) podría tener un identificador abierto para lo que está tratando de eliminar, en cualquier momento. Entonces tienes que intentarlo más tarde. Cuánto más tarde y cuántas veces lo intente dependerá de lo importante que sea que se elimine el objeto. Como indica MSDN ,

El código robusto de iteración de archivos debe tener en cuenta muchas complejidades del sistema de archivos.

Esta declaración inocente, suministrada solo con un enlace a la documentación de referencia de NTFS, debería poner los pelos de punta.

( Editar : mucho. Esta respuesta originalmente solo tenía la primera solución incompleta).

ryscl
fuente
11
Aparece llamando a Directory.Delete (ruta, verdadero) mientras la ruta o una de las carpetas / archivos bajo ruta está abierta o seleccionada en el Explorador de Windows arrojará una IOException. Cerrar Windows Explorer y volver a ejecutar mi código existente sin el try / catch sugerido anteriormente funcionó bien.
David Alpert
1
No puedo entender cómo y por qué funciona, pero funcionó para mí al configurar los atributos del archivo y escribir mi propia función recursiva no.
Stilgar
1
@CarlosLiu Porque le está dando a "Explorer la oportunidad de liberar el identificador de directorio"
Dmitry Gonchar
44
Lo que sucede es que el sistema le pide al Explorador que "libere el identificador de directorio", luego intenta eliminar el directorio. Si el identificador de directorio no se eliminó a tiempo, se genera una excepción y catchse ejecuta el bloque (mientras tanto, Explorer todavía está liberando el directorio, ya que no se ha enviado ningún comando para decirle que no lo haga). La llamada a Thread.Sleep(0)puede o no ser necesaria, ya que el catchbloque ya le ha dado al sistema un poco más de tiempo, pero proporciona un poco de seguridad adicional a bajo costo. Después de eso, Deletese llama al, con el directorio ya publicado.
Zachary Kniebel
1
@PandaWood en realidad solo este sueño (100) funcionó para mí. Dormir (0) no funcionó. No tengo idea de lo que está sucediendo y cómo resolver esto correctamente. Quiero decir, ¿qué pasa si depende de la carga del servidor y en el futuro debería haber 300 o 400? ¿Cómo saber eso? Debe ser otra forma correcta ...
Roman
43

Antes de continuar, verifique las siguientes razones que están bajo su control:

  • ¿La carpeta está configurada como un directorio actual de su proceso? En caso afirmativo, cámbielo a otra cosa primero.
  • ¿Has abierto un archivo (o cargado un archivo DLL) desde esa carpeta? (y olvidé cerrarlo / descargarlo)

De lo contrario, verifique las siguientes razones legítimas fuera de su control:

  • Hay archivos marcados como de solo lectura en esa carpeta.
  • No tiene permiso de eliminación para algunos de esos archivos.
  • El archivo o subcarpeta está abierto en Explorer u otra aplicación.

Si alguno de los problemas anteriores es el problema, debe comprender por qué sucede antes de intentar mejorar su código de eliminación. ¿Debería su aplicación eliminar archivos de solo lectura o inaccesibles? ¿Quién los marcó de esa manera y por qué?

Una vez que haya descartado las razones anteriores, todavía existe la posibilidad de fallas espurias. La eliminación fallará si alguien maneja cualquiera de los archivos o carpetas que se eliminan, y hay muchas razones por las cuales alguien puede enumerar la carpeta o leer sus archivos:

  • indexadores de búsqueda
  • antivirus
  • software de respaldo

El enfoque general para lidiar con fallas espurias es intentar varias veces, haciendo una pausa entre los intentos. Obviamente, no desea seguir intentándolo para siempre, por lo que debe rendirse después de un cierto número de intentos y lanzar una excepción o ignorar el error. Me gusta esto:

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
}

En mi opinión, un ayudante como ese debería usarse para todas las eliminaciones porque siempre son posibles fallas espurias. Sin embargo, DEBE ADAPTAR ESTE CÓDIGO A SU CASO DE USO, no solo copiarlo a ciegas.

Tuve fallas espurias para una carpeta de datos interna generada por mi aplicación, ubicada bajo% LocalAppData%, por lo que mi análisis es el siguiente:

  1. La carpeta está controlada únicamente por mi aplicación, y el usuario no tiene una razón válida para marcar cosas como de solo lectura o inaccesibles dentro de esa carpeta, por lo que no trato de manejar ese caso.

  2. No hay cosas valiosas creadas por el usuario allí, por lo que no hay riesgo de eliminar algo por la fuerza por error.

  3. Al ser una carpeta de datos interna, no espero que esté abierta en el explorador, al menos no siento la necesidad de manejar específicamente el caso (es decir, estoy manejando bien ese caso a través del soporte).

  4. Si todos los intentos fallan, elijo ignorar el error. En el peor de los casos, la aplicación no puede descomprimir algunos recursos más nuevos, se bloquea y solicita al usuario que se ponga en contacto con el soporte, lo cual es aceptable para mí siempre que no suceda con frecuencia. O, si la aplicación no se bloquea, dejará algunos datos antiguos, lo que nuevamente es aceptable para mí.

  5. Elijo limitar los reintentos a 500 ms (50 * 10). Este es un umbral arbitrario que funciona en la práctica; Quería que el umbral fuera lo suficientemente corto para que los usuarios no mataran la aplicación, pensando que ha dejado de responder. Por otro lado, medio segundo es tiempo de sobra para que el delincuente termine de procesar mi carpeta. A juzgar por otras respuestas SO que a veces se consideran Sleep(0)aceptables, muy pocos usuarios experimentarán más de un reintento.

  6. Vuelvo a intentar cada 50 ms, que es otro número arbitrario. Siento que si un archivo se está procesando (indexado, verificado) cuando intento eliminarlo, 50 ms es el momento adecuado para esperar que el procesamiento se complete en mi caso. Además, 50 ms es lo suficientemente pequeño como para no provocar una desaceleración notable; nuevamente, Sleep(0)parece ser suficiente en muchos casos, por lo que no queremos demorar demasiado.

  7. El código vuelve a intentar cualquier excepción de E / S. Normalmente no espero ninguna excepción para acceder a% LocalAppData%, así que elegí la simplicidad y acepté el riesgo de un retraso de 500 ms en caso de que ocurra una excepción legítima. Tampoco quería encontrar una manera de detectar la excepción exacta en la que quiero volver a intentar.

Andrey Tarantsov
fuente
77
PPS Unos meses más tarde, me complace informar que este fragmento de código (algo loco) ha resuelto completamente el problema. Las solicitudes de soporte sobre este problema se han reducido a cero (de aproximadamente 1-2 por semana).
Andrey Tarantsov
1
+0 Si bien este es un más robusto y menos 'aquí está; la solución perfecta para usted 'que stackoverflow.com/a/7518831/11635 , para mí se aplica lo mismo: programación por coincidencia, manejo con cuidado. Un punto útil incorporado en su código es que si va a volver a intentarlo, debe considerar que está en una carrera con la ambigüedad de si el Directorio se ha "ido" desde el último intento [y un Directory.Existsguardia niave lo haría no resolver eso.]
Ruben Bartelink
1
me encanta ... no sé lo que estoy haciendo que esto siempre es un punto de dolor para mí ... pero no es porque tengo el directorio abierto en el explorador ... no hay mucho alboroto en Internet sobre esto más -o-menos de fallo ... al menos yo y Andrey tengo una manera de tratar con él :)
TCC
2
@RubenBartelink OK, así que creo que podemos estar de acuerdo en esto: publicar un código que funcione para una aplicación específica (y nunca fue adecuado para cada caso) ya que una respuesta SO será perjudicial para muchos novatos y / o desarrolladores ignorantes. Lo di como punto de partida para la personalización, pero sí, algunas personas lo van a usar como está, y eso es algo malo.
Andrey Tarantsov
2
@nopara No necesitas la comparación; Si estamos fuera del circuito, hemos fallado. Y sí, en muchos casos querrá lanzar una excepción, luego agregar el código de manejo de errores apropiado en la pila, probablemente con un mensaje visible para el usuario.
Andrey Tarantsov
18

Respuesta asincrónica moderna

La respuesta aceptada es simplemente incorrecta, podría funcionar para algunas personas porque el tiempo necesario para obtener los archivos del disco libera lo que sea que bloqueaba los archivos. El hecho es que esto sucede porque los archivos se bloquean por algún otro proceso / flujo / acción. Las otras respuestas usan Thread.Sleep(Yuck) para volver a intentar eliminar el directorio después de un tiempo. Esta pregunta debe revisarse con una respuesta más 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;
}

Pruebas unitarias

Estas pruebas muestran un ejemplo de cómo un archivo bloqueado puede hacer Directory.Deleteque falle y cómo el TryDeleteDirectorymétodo anterior soluciona el 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);
        }
    }
}
Muhammad Rehan Saeed
fuente
¿Puedes ampliar lo que quieres decir con "moderno"? ¿Cuáles son los beneficios de su enfoque? ¿Por qué los otros, en tu opinión, están equivocados?
TinyRacoon
1
Otros no están equivocados. Simplemente usan API anteriores como las Thread.Sleepque debes evitar hoy y usar async/ awaitwith en su Task.Delaylugar. Eso es comprensible, esta es una pregunta muy antigua.
Muhammad Rehan Saeed
Este enfoque no funcionará en VB.Net (al menos no con una conversión de línea por línea muy literal) debido aBC36943 'Await' cannot be used inside a 'Catch' statement, a 'Finally' statement, or a 'SyncLock' statement.
amonroejj
@amonroejj Debe estar utilizando una versión anterior. Eso fue arreglado.
Muhammad Rehan Saeed
Pequeña mejora en lugar de volver verdadero if (!Directory.Exists(directoryPath)) { return true; } await Task.Delay(millisecondsDelay); para esperar hasta que el directorio se haya ido realmente
fuchs777
16

Una cosa importante que debería mencionarse (lo agregué como comentario pero no se me permite) es que el comportamiento de la sobrecarga cambió de .NET 3.5 a .NET 4.0.

Directory.Delete(myPath, true);

A partir de .NET 4.0, elimina archivos en la carpeta en sí, pero NO en 3.5. Esto también se puede ver en la documentación de MSDN.

.NET 4.0

Elimina el directorio especificado y, si se indica, cualquier subdirectorio y archivo en el directorio.

.NET 3.5

Elimina un directorio vacío y, si se indica, cualquier subdirectorio y archivo en el directorio.

jettatore
fuente
3
Creo que es solo un cambio de documentación ... si elimina solo un "directorio vacío", ¿qué significaría eliminar también los archivos en el directorio, con el parámetro 2 °? Si está vacío, no hay archivos ...
Pisu
Me temo que estás asumiendo mal. Publiqué esto después de probar el código con ambas versiones de framework. Eliminar una carpeta no vacía en 3.5 arrojará una excepción.
jettatore
15

Tuve el mismo problema con Delphi. Y el resultado final fue que mi propia aplicación estaba bloqueando el directorio que quería eliminar. De alguna manera, el directorio se bloqueó cuando estaba escribiendo en él (algunos archivos temporales).

El problema fue que hice un simple cambio de directorio a su padre antes de eliminarlo.

Drejc
fuente
66
+1 ¡Ahora hay algo que msdn para Directory.Delete sí menciona!
Ruben Bartelink
3
¿Alguna solución final con una muestra de código fuente completa trabajando al respecto?
Kiquenet
11

Me sorprende que nadie haya pensado en este método simple no recursivo, que puede eliminar directorios que contienen archivos de solo lectura, sin necesidad de cambiar el atributo de solo lectura de cada uno de ellos.

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

(Cambie un poco para no disparar una ventana de cmd momentáneamente, que está disponible en todo Internet)

Piyush Soni
fuente
Es bueno compartir con nosotros, pero ¿sería tan amable de incluir el poco de cambio necesario para evitar disparar la ventana de cmd, en lugar de pedirnos que lo busquemos en la red?
ThunderGr
Esto no funciona En la misma situación en la que puedo eliminar el archivo desde el símbolo del sistema o el Explorador, el uso de este código para llamar a rmdir proporciona el código de salida 145 que se traduce como "El directorio no está vacío". Deja el directorio vacío pero también en su lugar, exactamente como Directory.Delete ("", verdadero)
Kevin Coulombe
@Kevin Coulombe, Humm ... ¿Estás seguro de que estás usando los modificadores / s / q?
Piyush Soni
1
@KevinCoulombe: Sí, deben ser esos componentes COM. Cuando intento a través de C # antiguo, funciona y elimina el directorio junto con los archivos dentro (solo lectura o no solo lectura).
Piyush Soni
55
Si comienza a confiar en componentes externos para lo que debería estar en el marco, entonces es una idea "menos que ideal" porque ya no es portátil (o más difícil). ¿Qué pasa si el exe no está allí? ¿O la opción / cambió? Si la solución de Jeremy Edwards funciona, entonces debería preferirse en mi humilde opinión
francés el
11

Puede reproducir el error ejecutando:

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

Al intentar eliminar el directorio 'b', arroja la IOException "El directorio no está vacío". Eso es estúpido ya que acabamos de eliminar el directorio 'c'.

Según tengo entendido, la explicación es que el directorio 'c' está marcado como eliminado. Pero la eliminación aún no se ha confirmado en el sistema. El sistema ha respondido que el trabajo está hecho, mientras que, de hecho, todavía se está procesando. El sistema probablemente espera que el explorador de archivos se centre en el directorio principal para confirmar la eliminación.

Si observa el código fuente de la función Eliminar ( http://referencesource.microsoft.com/#mscorlib/system/io/directory.cs ) verá que utiliza la función nativa Win32Native.RemoveDirectory. Este comportamiento de no esperar se observa aquí:

La función RemoveDirectory marca un directorio para su eliminación al cerrar. Por lo tanto, el directorio no se elimina hasta que se cierra el último identificador del directorio.

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

Dormir y reintentar es la solución. Cf la solución del ryascl.

Olivier de Rivoyre
fuente
8

Tuve esos problemas de permisos extraños al eliminar directorios de perfil de usuario (en C: \ Documents and Settings) a pesar de poder hacerlo en el shell de Explorer.

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

No tiene sentido para mí lo que hace una operación de "archivo" en un directorio, pero sé que funciona y eso es suficiente para mí.

p.campbell
fuente
2
Todavía no hay esperanza, cuando el directorio tiene muchos archivos y Explorer abre la carpeta que contiene esos archivos.
Ve el
3

Esta respuesta se basa en: https://stackoverflow.com/a/1703799/184528 . La diferencia con mi código es que solo repetimos muchos subdirectorios y archivos de eliminación cuando es necesario una llamada a Directory.Delete falla en un primer intento (lo que puede suceder debido a que el explorador de Windows mira un directorio).

    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);
        } 
    }
cdiggins
fuente
Entonces, ¿cómo se supone que debe eliminar la carpeta si había un UnauthorizedAccessException? Simplemente lanzaría, de nuevo. Y otra vez. Y de nuevo ... Porque cada vez irá al catchy llamará a la función nuevamente. A Thread.Sleep(0);no cambia sus permisos. Simplemente debe registrar el error y fallar con gracia, en ese punto. Y este bucle continuará siempre que el (sub) directorio esté abierto; no lo cierra mediante programación. ¿Estamos preparados para dejar que haga esto mientras esas cosas se dejen abiertas? ¿Hay una mejor manera?
vapcguy
Si hay un UnauthorizedAccessException, intentará eliminar cada archivo manualmente. Por lo tanto, continúa progresando atravesando la estructura del directorio. Sí, potencialmente todos los archivos y directorios arrojarán la misma excepción, pero esto también puede ocurrir simplemente porque el explorador tiene un identificador (consulte stackoverflow.com/a/1703799/184528 ) Cambiaré el "tryAgain" a "second Try" para que quede más claro.
cdiggins el
Para responder más sucintamente, pasa "verdadero" y ejecuta una ruta de código diferente.
cdiggins
Bien, vi su edición, pero mi punto no es con la eliminación de archivos, sino con la eliminación del directorio. Escribí un código en el que podía hacer esencialmente Process.Kill()cualquier proceso por el que un archivo pudiera estar bloqueado y borrar los archivos. El problema con el que me encuentro es al eliminar un directorio donde uno de esos archivos todavía estaba abierto (consulte stackoverflow.com/questions/41841590/… ). Entonces, volviendo a este bucle, no importa qué más esté haciendo, si lo hace Directory.Delete()en esa carpeta nuevamente, todavía fallará si ese identificador no se puede liberar.
vapcguy
Y lo mismo ocurriría si se UnauthorizedAccessExceptionborraran los archivos (suponiendo que esto estuviera permitido, porque para acceder a ese código, falló Directory.Delete()) no mágicamente le da permiso para eliminar el directorio.
vapcguy
3

Ninguna de las soluciones anteriores funcionó bien para mí. Terminé usando una versión editada de la solución @ryascl como se muestra a continuación:

    /// <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);
        }
    }
cahit beyaz
fuente
2

¿Es posible que tenga una condición de carrera donde otro hilo o proceso está agregando archivos al directorio:

La secuencia sería:

Proceso de eliminación A:

  1. Vaciar el directorio
  2. Eliminar el directorio (ahora vacío).

Si alguien más agrega un archivo entre 1 y 2, ¿tal vez 2 arrojaría la excepción enumerada?

Douglas Leeder
fuente
2

He pasado algunas horas para resolver este problema y otras excepciones al eliminar el directorio. Esta es mi solucion

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

Este código tiene un pequeño retraso, que no es importante para mi aplicación. Pero tenga cuidado, la demora puede ser un problema para usted si tiene muchos subdirectorios dentro del directorio que desea eliminar.

Demid
fuente
8
-1 ¿De qué se trata el retraso? No hay programación por coincidencia por favor!
Ruben Bartelink
1
@ Rubén No dije que te equivocases al respecto. Acabo de decir que rechazarlo solo por este es un duro castigo. Estoy de acuerdo con usted, sin embargo, los 4 votos a favor no dieron como resultado 4 votos a favor. También votaría tu comentario, pero no rechazaría la respuesta debido a un retraso inexplicable :)
ThunderGr
1
@RubenBartelink y otros: aunque no me gusta específicamente este código (he publicado otra solución con un enfoque similar), el retraso aquí es razonable. Es muy probable que el problema esté fuera del control de la aplicación; quizás otra aplicación vuelva a escanear el FS periódicamente, bloqueando así la carpeta por cortos períodos de tiempo. El retraso resuelve el problema, haciendo que el informe de errores cuente hasta cero. ¿A quién le importa si no tenemos una idea de la causa raíz?
Andrey Tarantsov
1
@RubenBartelink De hecho, cuando lo piensa, no utilizar un enfoque de retraso y reintento durante la eliminación del directorio NTFS es una solución irresponsable aquí. Cualquier tipo de recorrido de archivo en curso bloquea la eliminación, por lo que es probable que falle tarde o temprano. Y no puede esperar que todas las herramientas de búsqueda, copia de seguridad, antivirus y administración de archivos de terceros permanezcan fuera de su carpeta.
Andrey Tarantsov
1
@RubenBartelink Otro ejemplo, digamos que da un retraso de 100 ms, y el mayor tiempo de bloqueo de cualquier software en la PC de destino es el software AV = 90 ms. Digamos que también tiene un software de respaldo que bloquea archivos durante 70 ms. Ahora el AV bloquea un archivo, su aplicación espera 100 ms, lo que normalmente está bien, pero luego encuentra otro bloqueo porque el software de respaldo comienza a tomar el archivo en la marca de 70 ms del escaneo AV, por lo que tomará otros 40 ms para liberar el archivo. Entonces, aunque el software AV tarda más y sus 100 ms son normalmente más largos que cualquiera de las 2 aplicaciones, aún debe tener en cuenta cuándo comienza en el medio.
vapcguy
2

La eliminación recursiva de directorios que no elimina archivos es ciertamente inesperada. Mi solución para eso:

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

Experimenté casos en los que esto ayudó, pero en general, Directory.Delete elimina los archivos dentro de los directorios tras la eliminación recursiva, como se documenta en msdn .

De vez en cuando me encuentro con este comportamiento irregular también como usuario del Explorador de Windows: a veces no puedo eliminar una carpeta (creo que el mensaje sin sentido es "acceso denegado"), pero cuando profundizo y elimino los elementos inferiores, puedo eliminar el superior artículos también. Entonces, supongo que el código anterior trata con una anomalía del sistema operativo, no con un problema de biblioteca de clase base.

citykid
fuente
1

El directorio o un archivo en él está bloqueado y no se puede eliminar. Encuentre al culpable que lo bloquea y vea si puede eliminarlo.

Vilx-
fuente
T1000 a usuario-con-carpeta-abierta: "¡Has terminado!"
vapcguy
1

Parece que tener la ruta o subcarpeta seleccionada en el Explorador de Windows es suficiente para bloquear una sola ejecución de Directory.Delete (ruta, verdadero), arrojando una IOException como se describió anteriormente y muriendo en lugar de arrancar el Explorador de Windows en una carpeta principal y proceder como esperado.

David Alpert
fuente
Esto parece haber sido mi problema. Tan pronto como cerré el Explorer y volví a correr, no hubo excepción. Incluso seleccionar a los padres de los padres no fue suficiente. Realmente tuve que cerrar Explorer.
Scott Marlowe
Sí, esto sucede y es una causa. Entonces, ¿alguna idea de cómo tratarlo programáticamente, o la respuesta es siempre asegurarse de que los 1000 usuarios tengan esa carpeta cerrada?
vapcguy
1

Tuve este problema hoy. Estaba sucediendo porque tenía el explorador de Windows abierto en el directorio que intentaba eliminarse, lo que provocó que la llamada recursiva fallara y, por lo tanto, la IOException. Asegúrese de que no haya identificadores abiertos en el directorio.

Además, MSDN tiene claro que no tiene que escribir su propia recusación: http://msdn.microsoft.com/en-us/library/fxeahc5f.aspx

GrokSrc
fuente
1

He tenido este mismo problema con Windows Workflow Foundation en un servidor de compilación con TFS2012. Internamente, el flujo de trabajo llamado Directory.Delete () con el indicador recursivo establecido en verdadero. Parece estar relacionado con la red en nuestro caso.

Estábamos eliminando una carpeta de colocación binaria en un recurso compartido de red antes de volver a crearla y volver a llenarla con los últimos archivos binarios. Cualquier otra construcción fallaría. Al abrir la carpeta de colocación después de una compilación fallida, la carpeta estaba vacía, lo que indica que todos los aspectos de la llamada Directory.Delete () fueron exitosos, excepto para eliminar el directorio actual.

El problema parece ser causado por la naturaleza asincrónica de las comunicaciones de archivos de red. El servidor de compilación le dijo al servidor de archivos que eliminara todos los archivos y el servidor de archivos informó que lo había hecho, a pesar de que no estaba completamente terminado. Luego, el servidor de compilación solicitó que se eliminara el directorio y el servidor de archivos rechazó la solicitud porque no había terminado de eliminar por completo los archivos.

Dos posibles soluciones en nuestro caso:

  • Construya la eliminación recursiva en nuestro propio código con demoras y verificaciones entre cada paso
  • Vuelva a intentarlo hasta X veces después de una IOException, demorando antes de volver a intentarlo

El último método es rápido y sucio, pero parece hacer el truco.

Shaun
fuente
1

Esto se debe a FileChangesNotifications.

Sucede desde ASP.NET 2.0. Cuando elimina alguna carpeta dentro de una aplicación, se reinicia . Puede verlo usted mismo, utilizando ASP.NET Health Monitoring .

Simplemente agregue este código a su web.config / configuration / system.web:

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


Después de eso echa un vistazo Windows Log -> Application. Que esta pasando:

Cuando elimina la carpeta, si hay alguna subcarpeta, Delete(path, true)primero se elimina la subcarpeta. Es suficiente para FileChangesMonitor saber acerca de la eliminación y cerrar su aplicación. Mientras tanto, su directorio principal aún no se ha eliminado. Este es el evento de Log:


ingrese la descripción de la imagen aquí


Delete() no terminó su trabajo y debido a que la aplicación se está cerrando, genera una excepción:

ingrese la descripción de la imagen aquí

Cuando no tiene ninguna subcarpeta en una carpeta que está eliminando, Delete () simplemente elimina todos los archivos y esa carpeta, la aplicación también se reinicia, pero no obtiene ninguna excepción , porque el reinicio de la aplicación no interrumpe nada. Pero aún así, pierde todas las sesiones en proceso, la aplicación no responde a las solicitudes al reiniciar, etc.

¿Ahora que?

Hay algunas soluciones y ajustes para deshabilitar este comportamiento, Directory Junction , Desactivar FCN con Registry , Detener FileChangesMonitor usando Reflection (ya que no hay un método expuesto) , pero no todos parecen estar en lo correcto, porque FCN está ahí para un razón. Se ocupa de la estructura de su aplicación , que no es la estructura de sus datos . La respuesta corta es: coloque las carpetas que desea eliminar fuera de su aplicación. FileChangesMonitor no recibirá notificaciones y su aplicación no se reiniciará cada vez. No obtendrás excepciones. Para que sean visibles desde la web, hay dos formas:

  1. Haga un controlador que maneje las llamadas entrantes y luego devuelva los archivos al leer desde la carpeta fuera de una aplicación (fuera de wwwroot).

  2. Si su proyecto es grande y el rendimiento es lo más importante, configure un servidor web pequeño y rápido para servir contenido estático. Por lo tanto, dejará a IIS su trabajo específico. Podría estar en la misma máquina (mangosta para Windows) u otra máquina (nginx para Linux). La buena noticia es que no tiene que pagar una licencia adicional de Microsoft para configurar el servidor de contenido estático en Linux.

Espero que esto ayude.

romano
fuente
1

Como se mencionó anteriormente, la solución "aceptada" falla en los puntos de análisis, pero la gente todavía la marca (???). Hay una solución mucho más corta que replica adecuadamente la funcionalidad:

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 sé, no maneja los casos de permisos mencionados más adelante, pero para todos los efectos, FAR BETTER proporciona la funcionalidad esperada del directorio original / stock.Delete () - y con mucho menos código también .

Puede continuar el procesamiento de forma segura porque el antiguo directorio estará fuera del camino ... incluso si no se ha ido porque el 'sistema de archivos todavía se está poniendo al día' (o cualquier excusa que MS haya dado para proporcionar una función rota) .

Como beneficio, si sabe que su directorio de destino es grande / profundo y no quiere esperar (o molestarse con excepciones) la última línea se puede reemplazar con:

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

Aún es seguro seguir trabajando.

Robar
fuente
2
¿Se puede simplificar su asignación mediante: string tfilename = Path.Combine (Path.GetDirectoryName (target), Path.GetRandomFileName ());
Pete
1
Tengo que estar de acuerdo con Pete. El código tal como está escrito no agregará el separador. Tomó mi camino de \\server\C$\diry lo hizo \\server\C$asf.yuw. Como resultado, recibí un error en el Directory.Move()- Source and destination path must have identical roots. Move will not work across volumes. Funcionó bien una vez que usé el código de Pete, EXCEPTO ninguno de los controladores para cuando hay archivos bloqueados o directorios abiertos, por lo que nunca llega al ThreadPoolcomando.
vapcguy
PRECAUCIÓN: Esta respuesta solo debe usarse con recursive = true. Cuando es falso, esto moverá el directorio incluso si no está vacío. Lo cual sería un error; El comportamiento correcto en ese caso es lanzar una excepción y dejar el directorio como estaba.
ToolmakerSteve
1

Este problema puede aparecer en Windows cuando hay archivos en un directorio (o en cualquier subdirectorio) cuya longitud de ruta es mayor que 260 símbolos.

En tales casos, debe eliminar en \\\\?\C:\mydirlugar de C:\mydir. Sobre el límite de 260 símbolos puede leer aquí .

RehénBrain
fuente
1

He resuelto con esta técnica milenaria (puedes dejar el Hilo. Dormir solo en la captura)

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);
Mauricio Rdz
fuente
0

Si el directorio actual de su aplicación (o de cualquier otra aplicación) es el que está tratando de eliminar, no será un error de infracción de acceso, pero el directorio no está vacío. Asegúrese de que no sea su propia aplicación cambiando el directorio actual; Además, asegúrese de que el directorio no esté abierto en algún otro programa (por ejemplo, Word, Excel, Total Commander, etc.). La mayoría de los programas se cd en el directorio del último archivo abierto, lo que causaría eso.

configurador
fuente
0

en el caso de archivos de red, Directory.DeleteHelper (recursive: = true) puede causar IOException que se debe a la demora de eliminar el archivo

muchedumbre
fuente
0

Creo que hay un archivo abierto por alguna secuencia que desconoce. Tuve el mismo problema y lo resolví cerrando todas las secuencias que apuntaban al directorio que quería eliminar.

Ahmad Moussa
fuente
0

Este error ocurre si algún archivo o directorio se considera en uso. Es un error engañoso. Verifique si tiene ventanas de explorador o de línea de comandos abiertas en cualquier directorio del árbol o en un programa que esté usando un archivo en ese árbol.

allen1
fuente
0

Resolví una posible instancia del problema declarado cuando los métodos eran asíncronos y se codificaban así:

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

Con este:

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

¿Conclusión? Hay un aspecto asincrónico de deshacerse del identificador utilizado para verificar la existencia con el que Microsoft no ha podido hablar. Es como si el método asincrónico dentro de una instrucción if tuviera la instrucción if actuando como una instrucción using.

Pat Pattillo
fuente
0

No tiene que crear un método adicional para la recursividad o eliminar archivos dentro de la carpeta adicional. Todo esto haciendo automáticamente llamando

DirectoryInfo.Delete ();

Los detalles están aquí .

Algo como esto funciona bastante bien:

  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
    }

pasando true como variable para eliminar el método, eliminará los subarchivos y la subcarpeta con archivos también.

nzrytmn
fuente
-2

Ninguna de las respuestas anteriores funcionó para mí. Parece que el uso de mi propia aplicación DirectoryInfoen el directorio de destino estaba causando que permaneciera bloqueado.

Forzar la recolección de basura pareció resolver el problema, pero no de inmediato. Unos pocos intentos de eliminar cuando sea necesario.

Tenga en cuenta Directory.Existsque puede desaparecer después de una excepción. No sé por qué se retrasó la eliminación para mí (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.");
Reactgular
fuente
1
-1 Programación por coincidencia. ¿Qué objeto hace qué cuando GC'd? ¿Es esto de alguna manera un buen consejo general? (Le creo cuando dice que tuvo un problema y que utilizó este código y siente que no tiene un problema ahora, pero ese no es el punto)
Ruben Bartelink
@RubenBartelink Estoy de acuerdo. Es un hack. Código de vudú que hace algo cuando no está claro qué está resolviendo o cómo. Me encantaría una solución adecuada.
Reactgular
1
Mi problema es que todo lo que agrega más allá de stackoverflow.com/a/14933880/11635 es altamente especulativo. Si pudiera, estaría dando un -1 por duplicación y un -1 por especulación / programación por coincidencia. La aspersión GC.Collectes a) solo un mal consejo yb) no es una causa general suficientemente común de directorios bloqueados para merecer inclusión aquí. Solo elige uno de los otros y no siembres más confusión en la mente de lectores inocentes
Ruben Bartelink
3
Use GC.WaitForPendingFinalizers (); después de GC.Collect (); Esto funcionará como se esperaba.
Heiner
No estoy seguro, no probado, pero quizás sea mejor hacer algo con una usingdeclaración, entonces: 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

agregue verdadero en el segundo parámetro.

Directory.Delete(path, true);

Eliminará todo.

Santiago Medina Chaverra
fuente
1
Directory.Delete (ruta, verdadero); fue la pregunta original
Pisu