Não é possível excluir o diretório com Directory.Delete (caminho, verdadeiro)


383

Estou usando o .NET 3.5, tentando excluir recursivamente um diretório usando:

Directory.Delete(myPath, true);

Meu entendimento é que isso deve ocorrer se os arquivos estiverem em uso ou houver um problema de permissão, mas, caso contrário, ele deverá excluir o diretório e todo o seu conteúdo.

No entanto, ocasionalmente entendo isso:

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

Não estou surpreso que o método às vezes seja lançado, mas fico surpreso ao receber essa mensagem específica quando a recursiva é verdadeira. (Eu sei que o diretório não está vazio.)

Existe um motivo para ver isso em vez de AccessViolationException?


13
Você não veria AccessViolationException - isso é para operações de ponteiro inválidas, não para acesso ao disco.
Joe White

11
Isso parece ser algum tipo de problema de E / S, além de o diretório não estar vazio, como identificadores de arquivos abertos ou algo assim. Eu tentaria usar a opção de exclusão recursiva e, em seguida, capturar IOException, procurar e fechar qualquer identificador de arquivo aberto e tentar novamente. Há uma discussão sobre isso aqui: stackoverflow.com/questions/177146/...
Dan Csharpster

Respostas:


231

Nota do editor: Embora esta resposta contenha algumas informações úteis, ela é factualmente incorreta sobre o funcionamento de Directory.Delete. Por favor, leia os comentários para esta resposta e outras respostas para esta pergunta.


Eu já tive esse problema antes.

A raiz do problema é que essa função não exclui arquivos que estão dentro da estrutura de diretórios. Portanto, o que você precisa fazer é criar uma função que exclua todos os arquivos da estrutura de diretórios e todos os diretórios antes de remover o próprio diretório. Eu sei que isso vai contra o segundo parâmetro, mas é uma abordagem muito mais segura. Além disso, você provavelmente desejará remover os atributos de acesso somente leitura dos arquivos antes de excluí-los. Caso contrário, isso gerará uma exceção.

Basta inserir esse código no seu projeto.

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

Além disso, para mim, eu adiciono pessoalmente uma restrição em áreas da máquina que podem ser excluídas porque você deseja que alguém chame essa função C:\WINDOWS (%WinDir%)ou C:\.


117
Isso não faz sentido. Directory.Delete (myPath, true) é uma sobrecarga que exclui todos os arquivos que estão dentro da estrutura de diretórios. Se você quer errar, errar com a resposta de Ryan S.
Sig. Tolleranza

35
+1 porque, embora Directory.Delete () exclua arquivos dentro de seus subdiretórios (com recursive = true), ele gera uma "IOException: o diretório não está vazio" se um dos subdiretórios ou arquivos for somente leitura. Portanto, esta solução funciona melhor que Directory.Delete ()
Anthony Brien

17
Sua afirmação de que Directory.Delete(path, true)não exclui arquivos está errada. Veja MSDN msdn.microsoft.com/en-us/library/fxeahc5f.aspx
Konstantin Spirin

20
-1 Alguém pode colocar um marcador claro de que a validade dessa abordagem está muito em dúvida. Se Directory.Delete(string,bool)falhar, algo está bloqueado ou não autorizado e não existe um tamanho único para todas as soluções para esse problema. As pessoas precisam abordar essa questão em seu contexto, e nós não devemos crescer muito peludo - lance todas as idéias para o problema (com tentativas e engolir exceções) e esperamos um bom resultado.
Ruben Bartelink

37
Cuidado com esta abordagem se seu diretório sua exclusão tem atalhos / links simbólicos para outras pastas - você pode acabar excluindo mais do que o esperado
Chanakya

182

Se você estiver tentando excluir o diretório recursivamente ae o diretório a\bestiver aberto no Explorer, bele será excluído, mas o erro 'diretório não está vazio' será exibido, amesmo que esteja vazio quando você olhar. O diretório atual de qualquer aplicativo (incluindo o Explorer) mantém um identificador no diretório . Quando você liga Directory.Delete(true), ele exclui de baixo para cima:, bentão a. Se bestiver aberto no Explorer, o Explorer detectará a exclusão b, alterará o diretório para cima cd ..e limpará os identificadores abertos. Como o sistema de arquivos opera de forma assíncrona, a Directory.Deleteoperação falha devido a conflitos com o Explorer.

Solução incompleta

Originalmente, publiquei a seguinte solução, com a idéia de interromper o segmento atual para permitir que o tempo do Explorer libere o identificador de diretório.

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

Mas isso só funciona se o diretório aberto for o filho imediato do diretório que você está excluindo. Se a\b\c\destiver aberto no Explorer e você o usar a, essa técnica falhará após excluir de c.

Uma solução um pouco melhor

Esse método manipulará a exclusão de uma estrutura de diretórios profunda, mesmo que um dos diretórios de nível inferior esteja aberto no 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);
    }
}

Apesar do trabalho extra de repetir por conta própria, ainda temos que lidar com o UnauthorizedAccessExceptionque pode ocorrer ao longo do caminho. Não está claro se a primeira tentativa de exclusão está abrindo caminho para a segunda, bem-sucedida, ou se é apenas o atraso de tempo introduzido ao lançar / capturar uma exceção que permite ao sistema de arquivos recuperar o atraso.

Você pode reduzir o número de exceções lançadas e capturadas em condições típicas adicionando uma Thread.Sleep(0)no início do trybloco. Além disso, existe o risco de que, sob carga pesada do sistema, você possa passar pelas Directory.Deletetentativas e falhar. Considere esta solução como um ponto de partida para uma exclusão recursiva mais robusta.

Resposta geral

Esta solução aborda apenas as peculiaridades da interação com o Windows Explorer. Se você deseja uma operação de exclusão sólida, lembre-se de que qualquer coisa (antivírus, qualquer que seja) pode ter um identificador aberto para o que você está tentando excluir, a qualquer momento. Então você tem que tentar novamente mais tarde. Quanto mais tarde e quantas vezes você tenta depende de quão importante é que o objeto seja excluído. Como o MSDN indica ,

O código robusto de iteração de arquivo deve levar em consideração muitas complexidades do sistema de arquivos.

Essa declaração inocente, fornecida apenas com um link para a documentação de referência do NTFS, deve fazer você se arrepiar.

( Edit : Muita. Esta resposta originalmente tinha apenas a primeira solução incompleta.)


11
Ele aparece chamando Directory.Delete (caminho, verdadeiro) enquanto o caminho ou uma das pastas / arquivos no caminho estiver aberto ou selecionado no Windows Explorer lançará uma IOException. Fechar o Windows Explorer e executar novamente o código existente sem a tentativa / captura sugerida acima funcionou bem.
David Alpert

11
Não consigo entender como e por que funciona, mas funcionou para mim ao definir atributos de arquivo e escrever minha própria função recursiva não.
Stilgar

11
@CarlosLiu Porque está dando "Explorador uma chance para soltar a alça diretório"
Dmitry Gonchar

4
O que está acontecendo é que o sistema pede ao Explorer para "liberar o identificador de diretório" e tenta excluir o diretório. Se o identificador de diretório não tiver sido excluído a tempo, uma exceção será gerada e o catchbloco será executado (enquanto isso, o Explorer ainda está liberando o diretório, pois nenhum comando foi enviado para solicitar que ele não o faça). A chamada para Thread.Sleep(0)pode ou não ser necessária, pois o catchbloco já deu ao sistema um pouco mais de tempo, mas fornece um pouco de segurança extra por um baixo custo. Depois disso, Deleteé chamado, com o diretório já liberado.
Zachary Kniebel

11
@PandaWood, na verdade, apenas este sono (100) funcionou para mim. Dormir (0) não funcionou. Não tenho ideia do que está acontecendo e como resolver isso corretamente. Quero dizer, e se depender da carga do servidor e no futuro houver 300 ou 400? Como saber disso. Deve ser uma outra maneira adequada ... #
Roman

43

Antes de prosseguir, verifique os seguintes motivos que estão sob seu controle:

  • A pasta está definida como um diretório atual do seu processo? Se sim, mude para outra coisa primeiro.
  • Você abriu um arquivo (ou carregou uma DLL) dessa pasta? (e esqueceu de fechar / descarregar)

Caso contrário, verifique os seguintes motivos legítimos fora do seu controle:

  • Existem arquivos marcados como somente leitura nessa pasta.
  • Você não tem permissão para excluir alguns desses arquivos.
  • O arquivo ou subpasta é aberto no Explorer ou em outro aplicativo.

Se algum dos itens acima for o problema, você deve entender por que isso acontece antes de tentar melhorar seu código de exclusão. Caso a sua aplicação ser a exclusão de arquivos somente leitura ou inacessíveis? Quem os marcou dessa maneira, e por quê?

Depois de descartar as razões acima, ainda existe a possibilidade de falhas espúrias. A exclusão falhará se alguém manipular um dos arquivos ou pastas que estão sendo excluídos, e há muitos motivos pelos quais alguém pode estar enumerando a pasta ou lendo seus arquivos:

  • indexadores de pesquisa
  • anti-vírus
  • software de backup

A abordagem geral para lidar com falhas espúrias é tentar várias vezes, fazendo uma pausa entre as tentativas. Obviamente, você não quer continuar tentando para sempre, portanto, desista após um certo número de tentativas e lance uma exceção ou ignore o erro. Como isso:

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
}

Na minha opinião, um ajudante como esse deve ser usado para todas as exclusões, porque falhas espúrias são sempre possíveis. No entanto, VOCÊ DEVE ADAPTAR ESTE CÓDIGO AO SEU CASO DE USO, e não apenas copiá-lo cegamente.

Eu tive falhas espúrias em uma pasta de dados interna gerada pelo meu aplicativo, localizada em% LocalAppData%, portanto, minha análise é assim:

  1. A pasta é controlada apenas pelo meu aplicativo e o usuário não tem motivos válidos para marcar as coisas como somente leitura ou inacessíveis dentro dessa pasta, portanto, não tento lidar com esse caso.

  2. Não há material valioso criado pelo usuário, por isso não há risco de excluir algo com força por engano.

  3. Sendo uma pasta de dados interna, não espero que seja aberta no explorer, pelo menos não sinto a necessidade de lidar especificamente com o caso (ou seja, estou lidando bem com o caso através do suporte).

  4. Se todas as tentativas falharem, eu escolho ignorar o erro. Na pior das hipóteses, o aplicativo falha ao descompactar alguns recursos mais recentes, trava e solicita que o usuário entre em contato com o suporte, o que é aceitável para mim desde que isso não ocorra com frequência. Ou, se o aplicativo não travar, ele deixará alguns dados antigos para trás, o que novamente é aceitável para mim.

  5. Eu escolho limitar as tentativas a 500ms (50 * 10). Este é um limite arbitrário que funciona na prática; Eu queria que o limite fosse curto o suficiente para que os usuários não matassem o aplicativo, pensando que ele parou de responder. Por outro lado, meio segundo é bastante tempo para o agressor terminar de processar minha pasta. A julgar por outras respostas do SO que às vezes consideram Sleep(0)aceitáveis, muito poucos usuários experimentam mais do que uma única tentativa.

  6. Repito a cada 50ms, que é outro número arbitrário. Sinto que, se um arquivo está sendo processado (indexado, verificado) quando tento excluí-lo, 50ms é o momento certo para esperar que o processamento seja concluído no meu caso. Além disso, 50ms é pequeno o suficiente para não resultar em uma desaceleração perceptível; novamente, Sleep(0)parece ser suficiente em muitos casos, por isso não queremos atrasar demais.

  7. O código tenta novamente todas as exceções de E / S. Normalmente, não espero exceções acessando% LocalAppData%, por isso escolhi a simplicidade e aceitei o risco de um atraso de 500ms no caso de uma exceção legítima. Também não queria descobrir uma maneira de detectar a exceção exata em que desejo tentar novamente.


7
PPS Alguns meses depois, fico feliz em informar que esse código (um tanto insano) resolveu completamente o problema. As solicitações de suporte sobre esse problema estão abaixo de zero (de cerca de 1 a 2 por semana).
Andrey Tarantsov

11
+0 Embora seja mais robusto e menos 'aqui está; a solução perfeita para você 'do que stackoverflow.com/a/7518831/11635 , para mim o mesmo se aplica - programação por coincidência - manuseie com cuidado. Um ponto útil incorporado ao seu código é que, se você quiser fazer uma nova tentativa, precisará considerar que está em uma corrida com a ambiguidade de saber se o Diretório foi 'ido' desde a última tentativa [e um Directory.Existsguarda niave não resolver essa].
Ruben Bartelink

11
adoro ... não sei o que estou fazendo que isso sempre é um problema para mim ... mas não é porque eu tenho o diretório aberto no explorer ... não há muito tumulto na internet sobre isso mais -ou-less bug ... pelo menos eu e Andrey tem uma maneira de lidar com ele :)
TCC

2
@RubenBartelink OK, então acho que podemos concordar com isso: postar um pedaço de código que funcione para um aplicativo específico (e nunca foi concebido para ser adequado a todos os casos), pois uma resposta SO será um desserviço para muitos iniciantes e / ou desenvolvedores ignorantes. Eu dei como ponto de partida para a personalização, mas sim, algumas pessoas vão usá-lo como está, e isso é uma coisa ruim.
Andrey Tarantsov

2
@nopara Você não precisa da comparação; se estamos fora do circuito, falhamos. E sim, em muitos casos, você desejará lançar uma exceção e adicionar o código de tratamento de erros apropriado na pilha, provavelmente com uma mensagem visível ao usuário.
Andrey Tarantsov

18

Resposta assíncrona moderna

A resposta aceita está simplesmente errada, pode funcionar para algumas pessoas, porque o tempo necessário para obter arquivos do disco libera o que estava bloqueando os arquivos. O fato é que isso acontece porque os arquivos são bloqueados por algum outro processo / fluxo / ação. As outras respostas usam Thread.Sleep(Yuck) para tentar excluir o diretório novamente após algum tempo. Esta questão precisa ser revisada com uma resposta mais 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;
}

Testes unitários

Esses testes mostram um exemplo de como um arquivo bloqueado pode causar Directory.Deletefalhas e como o TryDeleteDirectorymétodo acima corrige o 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);
        }
    }
}

Você pode expandir o que você entende por "moderno"? Quais são os benefícios da sua abordagem? Por que os outros, na sua opinião, estão errados?
TinyRacoon 25/10/19

11
Outros não estão errados. Eles apenas usam APIs antigas, como as Thread.Sleepque você deve evitar hoje e usa async/ awaitwith Task.Delay. Isso é compreensível, essa é uma pergunta muito antiga.
Muhammad Rehan Saeed

Essa abordagem não funcionará no VB.Net (pelo menos não com uma conversão linha a linha muito literal) devido aBC36943 'Await' cannot be used inside a 'Catch' statement, a 'Finally' statement, or a 'SyncLock' statement.
amonroejj 4/19/19

@amonroejj Você deve estar usando uma versão mais antiga. Isso foi consertado.
Muhammad Rehan Saeed

Pequena melhoria em vez de retornar true if (!Directory.Exists(directoryPath)) { return true; } await Task.Delay(millisecondsDelay); para esperar até que o diretório realmente desapareça
fuchs777

16

Uma coisa importante que deve ser mencionada (eu a adicionei como comentário, mas não tenho permissão) é que o comportamento da sobrecarga mudou do .NET 3.5 para o .NET 4.0.

Directory.Delete(myPath, true);

A partir do .NET 4.0, ele exclui arquivos da própria pasta, mas NÃO da 3.5. Isso também pode ser visto na documentação do MSDN.

.NET 4.0

Exclui o diretório especificado e, se indicado, quaisquer subdiretórios e arquivos no diretório.

.NET 3.5

Exclui um diretório vazio e, se indicado, quaisquer subdiretórios e arquivos no diretório.


3
Eu acho que é apenas uma alteração na documentação ... se ele excluir apenas um "diretório vazio", o que significaria excluir também arquivos no diretório, com o parâmetro 2 °? Se estiver vazio, não há arquivos ...
Pisu 16/02

Receio que você esteja assumindo errado. Publiquei isso depois de testar o código com as duas versões da estrutura. A exclusão de uma pasta não vazia no 3.5 lançará uma exceção.
jettatore

15

Eu tive o mesmo problema no Delphi. E o resultado final foi que meu próprio aplicativo estava bloqueando o diretório que eu queria excluir. De alguma forma, o diretório ficou bloqueado quando eu estava escrevendo nele (alguns arquivos temporários).

A captura 22 foi, eu fiz um diretório de mudança simples para seu pai antes de excluí-lo.


6
+1 Agora, existe algo que o msdn para Directory.Delete menciona!
Ruben Bartelink

3
alguma solução final com amostra de código fonte completa trabalhando nisso?
Kiquenet

11

Estou surpreso que ninguém tenha pensado nesse método simples e não recursivo, que pode excluir diretórios contendo arquivos somente leitura, sem precisar alterar o atributo somente leitura de cada um deles.

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

(Altere um pouco para não disparar uma janela cmd momentaneamente, disponível em toda a Internet)


É bom compartilhar conosco, mas você gostaria de incluir o pouco de alteração necessária para evitar o disparo da janela do cmd, em vez de nos solicitar a procurá-la pela rede?
ThunderGr

Isso não funciona. Na mesma situação em que posso excluir o arquivo de um prompt de comando ou do Explorer, usar esse código para chamar rmdir fornece o código de saída 145, que se traduz em "O diretório não está vazio". Ele deixa a pasta vazia, mas ainda no lugar também, exatamente como Directory.Delete ( "", true)
Kevin Coulombe

@ Kevin Coulombe, Humm ... Tem certeza de que está usando as opções / s / q?
Piyush Soni

11
@KevinCoulombe: Sim, devem ser esses componentes COM. Quando tento c # antigo simples, ele funciona e exclui o diretório junto com os arquivos dentro (somente leitura ou não somente leitura).
Piyush Soni

5
Se você começar a confiar em componentes externos para o que deveria estar na estrutura, é uma idéia "menos que o ideal", porque não é mais portátil (ou mais difícil). E se o exe não estiver lá? Ou a opção / mudou? Se a solução por Jeremy Edwards funciona, então deve ser IMHO preferido
frenchone

11

Você pode reproduzir o erro executando:

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

Ao tentar excluir o diretório 'b', ele lança a IOException "O diretório não está vazio". Isso é estúpido, pois acabamos de excluir o diretório 'c'.

Pelo meu entendimento, a explicação é que o diretório 'c' é carimbado como excluído. Mas a exclusão ainda não foi confirmada no sistema. O sistema respondeu que o trabalho foi concluído e, na verdade, ainda está em processamento. O sistema provavelmente espera que o gerenciador de arquivos se concentre no diretório pai para confirmar a exclusão.

Se você procurar o código fonte da função Excluir ( http://referencesource.microsoft.com/#mscorlib/system/io/directory.cs ), verá que ele usa a função nativa Win32Native.RemoveDirectory. Esse comportamento de não esperar é observado aqui:

A função RemoveDirectory marca um diretório para exclusão ao fechar. Portanto, o diretório não é removido até que o último identificador para o diretório seja fechado.

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

Dormir e tentar novamente é a solução. Cf a solução do ryascl.


8

Eu tive um problema estranho de permissão ao excluir diretórios de perfil de usuário (em C: \ Documents and Settings), apesar de poder fazê-lo no shell do Explorer.

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

Não faz sentido para mim o que uma operação de "arquivo" faz em um diretório, mas sei que funciona e é o suficiente para mim!


2
Ainda não há esperança, quando o diretório tem muitos arquivos e o Explorer está abrindo a pasta que contém esses arquivos.

3

Esta resposta é baseada em: https://stackoverflow.com/a/1703799/184528 . A diferença com o meu código é que apenas recursamos muitos subdiretórios e arquivos de exclusão quando necessário, uma chamada para o Directory.Delete falha em uma primeira tentativa (o que pode acontecer por causa do Windows Explorer procurar um diretório).

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

Então, como é possível excluir a pasta se houver um UnauthorizedAccessException? Seria apenas jogar de novo. E de novo. E novamente ... Porque cada vez que você acessa catche chama a função novamente. A Thread.Sleep(0);não altera suas permissões. Ele deve apenas registrar o erro e falhar normalmente, nesse ponto. E esse loop continuará enquanto o diretório (sub-) estiver aberto - ele não será fechado programaticamente. Estamos preparados para deixá-lo fazer isso enquanto essas coisas forem deixadas em aberto? Existe uma maneira melhor?
precisa saber é o seguinte

Se houver, UnauthorizedAccessExceptionele tentará excluir manualmente cada arquivo. Portanto, ele continua progredindo atravessando a estrutura de diretórios. Sim, potencialmente todos os arquivos e diretórios lançam a mesma exceção, mas isso também pode ocorrer simplesmente porque o explorer está segurando um identificador (consulte stackoverflow.com/a/1703799/184528 ) Alterarei o "tryAgain" para "secondTry" para deixar mais claro.
Cdggins

Para responder de maneira mais sucinta, ele passa "true" e executa um caminho de código diferente.
Cdggins

Certo, vi sua edição, mas meu ponto não é com a exclusão de arquivos, mas com a exclusão do diretório. Eu escrevi um código onde eu poderia fazer essencialmente Process.Kill()em qualquer processo pelo qual um arquivo pode ser bloqueado e excluí-lo. O problema que encontro é ao excluir um diretório em que um desses arquivos ainda estava aberto (consulte stackoverflow.com/questions/41841590/… ). Então, voltando a esse loop, não importa o que mais ele esteja fazendo, se fizer Directory.Delete()novamente nessa pasta, ainda falhará se esse identificador não puder ser liberado.
precisa saber é

E o mesmo ocorreria UnauthorizedAccessExceptiondesde que a exclusão de arquivos (supondo que isso fosse permitido, porque, para chegar a esse código, falhou Directory.Delete()), magicamente não lhe dá permissão para excluir o diretório.
precisa saber é

3

Não das soluções acima funcionou bem para mim. Acabei usando uma versão editada da solução @ryascl como abaixo:

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

É possível que você tenha uma condição de corrida em que outro encadeamento ou processo esteja adicionando arquivos ao diretório:

A sequência seria:

Deleter o processo A:

  1. Esvazie o diretório
  2. Exclua o diretório (agora vazio).

Se outra pessoa adicionar um arquivo entre 1 e 2, talvez 2 jogue a exceção listada?


2

Passei algumas horas para resolver esse problema e outras exceções com a exclusão do diretório. Esta é a minha solução

 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 tem um pequeno atraso, o que não é importante para o meu aplicativo. Mas tenha cuidado, o atraso pode ser um problema para você se você tiver muitos subdiretórios dentro do diretório que deseja excluir.


8
-1 Qual é o atraso? Nenhuma programação por coincidência, por favor!
Ruben Bartelink

11
@ Ruben Eu não disse que você está errado sobre isso. Acabei de dizer que votar apenas neste é um castigo severo. Eu concordo com você, no entanto, as 4 votações iniciais não resultaram em 4 votações negativas. Eu upvote seu comentário bem, mas eu não downvote a resposta por causa de um atraso inexplicável :)
ThunderGr

11
@ RubenBartelink e outros: embora eu não goste especificamente desse código (publiquei outra solução com uma abordagem semelhante), o atraso aqui é razoável. O problema provavelmente está fora do controle do aplicativo; talvez outro aplicativo revele o FS periodicamente, bloqueando a pasta por curtos períodos de tempo. O atraso resolve o problema, fazendo com que o relatório de erros seja zero. Quem se importa se não temos a menor idéia da causa raiz?
Andrey Tarantsov

11
@RubenBartelink De fato, quando você pensa sobre isso, não usar uma abordagem de atraso e repetição durante a exclusão do diretório NTFS é uma solução irresponsável aqui. Qualquer tipo de passagem de arquivo em andamento bloqueia a exclusão, por isso é provável que falhe mais cedo ou mais tarde. E você não pode esperar que todas as ferramentas de pesquisa, backup, antivírus e gerenciamento de arquivos de terceiros fiquem fora da sua pasta.
Andrey Tarantsov

11
@RubenBartelink Outro exemplo: digamos que você dê um atraso de 100ms, e o tempo de bloqueio mais alto de qualquer software no PC de destino é o software AV = 90ms. Digamos que também tenha um software de backup que bloqueie arquivos por 70ms. Agora, o AV bloqueia um arquivo, seu aplicativo aguarda 100 ms, o que normalmente é bom, mas encontra outro bloqueio, porque o software de backup começa a agarrar o arquivo na marca de 70 ms da verificação do AV e leva mais 40 ms para liberar o arquivo. Portanto, enquanto o software AV leva mais tempo e seus 100ms são normalmente mais longos do que qualquer um dos dois aplicativos, você ainda precisa contabilizar quando é iniciado no meio.
vapcguy

2

A exclusão recursiva de diretório que não exclui arquivos é certamente inesperada. Minha correção para isso:

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

Eu experimentei casos em que isso ajudava, mas geralmente, o Directory.Delete exclui arquivos dentro de diretórios após a exclusão recursiva, conforme documentado no msdn .

De tempos em tempos, também encontro esse comportamento irregular como usuário do Windows Explorer: Às vezes, não consigo excluir uma pasta (ela acha que a mensagem sem sentido é "acesso negado"), mas quando faço uma busca detalhada e excluo itens inferiores, posso excluir a parte superior. itens também. Então, acho que o código acima lida com uma anomalia do sistema operacional - não com um problema da biblioteca de classes base.


1

O diretório ou um arquivo nele está bloqueado e não pode ser excluído. Encontre o culpado que o bloqueia e veja se você pode eliminá-lo.


T1000 para usuário com pasta aberta: "Você terminou!"
vapcguy

1

Parece que ter o caminho ou subpasta selecionado no Windows Explorer é suficiente para bloquear uma única execução do Directory.Delete (caminho, true), lançando uma IOException conforme descrito acima e morrendo em vez de inicializar o Windows Explorer em uma pasta pai e proceder como esperado.


Este parece ter sido o meu problema. Assim que fechei o Explorer e executei novamente, sem exceção. Mesmo selecionar o pai dos pais não era suficiente. Eu tive que realmente fechar o Explorer.
21413 Scott Marlowe

Sim, isso acontece e é uma causa. Então, alguma idéia de como lidar programaticamente com isso ou a resposta é sempre sempre para garantir que todos os 1000 usuários tenham essa pasta fechada?
vapcguy

1

Eu tive esse problema hoje. Isso estava acontecendo porque eu tinha o Windows Explorer aberto no diretório que estava tentando ser excluído, causando a chamada recursiva de falha e, portanto, a IOException. Verifique se não há identificadores abertos para o diretório.

Além disso, o MSDN está claro que você não precisa escrever sua própria recusão: http://msdn.microsoft.com/en-us/library/fxeahc5f.aspx


1

Eu tive esse mesmo problema com o Windows Workflow Foundation em um servidor de compilação com o TFS2012. Internamente, o fluxo de trabalho chamado Directory.Delete () com o sinalizador recursivo definido como true. Parece estar relacionado à rede no nosso caso.

Estávamos excluindo uma pasta suspensa binária em um compartilhamento de rede antes de recriá-la e preenchê-la novamente com os binários mais recentes. Qualquer outra construção falharia. Ao abrir a pasta suspensa após uma falha na compilação, a pasta estava vazia, o que indica que todos os aspectos da chamada Directory.Delete () foram bem-sucedidos, exceto a exclusão do diretório real.

O problema parece ser causado pela natureza assíncrona das comunicações de arquivos de rede. O servidor de compilação disse ao servidor de arquivos para excluir todos os arquivos e o servidor de arquivos informou que tinha, mesmo que não estivesse completamente concluído. Em seguida, o servidor de compilação solicitou que o diretório fosse excluído e o servidor de arquivos rejeitou a solicitação porque ainda não havia concluído a exclusão dos arquivos.

Duas soluções possíveis no nosso caso:

  • Crie a exclusão recursiva em nosso próprio código com atrasos e verificações entre cada etapa
  • Repita até X vezes após uma IOException, dando um atraso antes de tentar novamente

O último método é rápido e sujo, mas parece fazer o truque.


1

Isso ocorre por causa de FileChangesNotifications.

Isso acontece desde o ASP.NET 2.0. Quando você exclui alguma pasta em um aplicativo, ela é reiniciada . Você pode vê-lo usando o ASP.NET Health Monitoring .

Basta adicionar este código ao seu web.config / configuration / system.web:

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


Depois disso, confira Windows Log -> Application. O que está acontecendo:

Quando você exclui uma pasta, se houver alguma subpasta, Delete(path, true)exclui a subpasta primeiro. É suficiente para o FileChangesMonitor saber sobre remoção e desligar o aplicativo. Enquanto isso, seu diretório principal ainda não foi excluído. Este é o evento do Log:


insira a descrição da imagem aqui


Delete() não terminou seu trabalho e, como o aplicativo está sendo desligado, gera uma exceção:

insira a descrição da imagem aqui

Quando você não possui nenhuma subpasta em uma pasta que está sendo excluída, Delete () exclui todos os arquivos e essa pasta, o aplicativo também é reiniciado, mas você não recebe nenhuma exceção , porque a reinicialização do aplicativo não interrompe nada. Ainda assim, você perde todas as sessões em processo, o aplicativo não responde a solicitações ao reiniciar etc.

E agora?

Existem algumas soluções alternativas e ajustes para desativar esse comportamento, Junção de Diretório , Desativando FCN com Registro , Interrompendo o FileChangesMonitor usando o Reflection (como não há método exposto) , mas nem todos parecem estar corretos, pois a FCN está lá para razão. Ele está cuidando da estrutura do seu aplicativo , que não é a estrutura dos seus dados . A resposta curta é: coloque as pastas que você deseja excluir fora do seu aplicativo. O FileChangesMonitor não receberá notificações e seu aplicativo não será reiniciado todas as vezes. Você não receberá exceções. Para torná-los visíveis na Web, existem duas maneiras:

  1. Crie um controlador que lide com as chamadas recebidas e depois entregue os arquivos lendo da pasta fora de um aplicativo (fora do wwwroot).

  2. Se o seu projeto for grande e o desempenho mais importante, configure um servidor da web pequeno e rápido separado para veicular conteúdo estático. Assim, você deixará para o IIS seu trabalho específico. Pode estar na mesma máquina (mangusto para Windows) ou em outra máquina (nginx para Linux). A boa notícia é que você não precisa pagar uma licença extra da Microsoft para configurar o servidor de conteúdo estático no linux.

Espero que isto ajude.


1

Como mencionado acima, a solução "aceita" falha nos pontos de nova análise - mas as pessoas ainda a marcam (???). Há uma solução muito mais curta que replica adequadamente a funcionalidade:

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

Eu sei, não lida com os casos de permissões mencionados posteriormente, mas para todos os efeitos, o FAR BETTER fornece a funcionalidade esperada do Directory.Delete / stock original / estoque () - e com muito menos código também .

Você pode continuar o processamento com segurança porque o diretório antigo estará fora do caminho ... mesmo que não tenha saído porque o 'sistema de arquivos ainda está atualizando' (ou qualquer outra desculpa que a MS deu para fornecer uma função quebrada) .

Como benefício, se você souber que o diretório de destino é grande / profundo e não deseja esperar (ou se preocupar com exceções), a última linha pode ser substituída por:

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

Você ainda está seguro para continuar trabalhando.


2
Sua tarefa pode ser simplificada por: string tfilename = Path.Combine (Path.GetDirectoryName (target), Path.GetRandomFileName ());
Pete

11
Eu tenho que concordar com Pete. O código escrito não adicionará o separador. Ele pegou meu caminho \\server\C$\dire o fez \\server\C$asf.yuw. Como resultado, recebi um erro no Directory.Move()- Source and destination path must have identical roots. Move will not work across volumes. Funcionou bem quando usei o código de Pete EXCEPT, nem manipula quando há arquivos bloqueados ou diretórios abertos - para que nunca chegue ao ThreadPoolcomando.
precisa saber é

CUIDADO: Esta resposta deve ser usada apenas com recursive = true. Quando falso, isso moverá o diretório, mesmo que não esteja vazio. O que seria um bug; O comportamento correto nesse caso é lançar uma exceção e deixar o diretório como estava.
Home

1

Esse problema pode aparecer no Windows quando houver arquivos em um diretório (ou em qualquer subdiretório) cujo tamanho do caminho seja maior que 260 símbolos.

Nesses casos, você precisa excluir em \\\\?\C:\mydirvez de C:\mydir. Sobre o limite de 260 símbolos, você pode ler aqui .


1

Eu resolvi com essa técnica milenar (você pode deixar o Thread.Sleep por conta própria na 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);

0

Se o diretório atual do seu aplicativo (ou de qualquer outro aplicativo) for o que você está tentando excluir, não será um erro de violação de acesso, mas um diretório não estará vazio. Verifique se não é seu próprio aplicativo alterando o diretório atual; Além disso, verifique se o diretório não está aberto em outro programa (por exemplo, Word, Excel, Total Commander, etc.). A maioria dos programas cdará para o diretório do último arquivo aberto, o que causaria isso.


0

no caso de arquivos de rede, Directory.DeleteHelper (recursive: = true) pode causar IOException, causado pelo atraso na exclusão do arquivo


0

Eu acho que existe um arquivo aberto por algum fluxo que você não conhece, tive o mesmo problema e o resolvi fechando todos os fluxos que apontavam para o diretório que eu queria excluir.


0

Este erro ocorre se qualquer arquivo ou diretório for considerado em uso. É um erro enganoso. Verifique se você possui alguma janela do explorer ou da linha de comando aberta em qualquer diretório da árvore ou em um programa que esteja usando um arquivo nessa árvore.


0

Resolvi uma possível instância do problema declarado quando os métodos eram assíncronos e codificados da seguinte maneira:

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

Com isso:

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

Conclusão? Há algum aspecto assíncrono de se livrar do identificador usado para verificar a existência com a qual a Microsoft não conseguiu falar. É como se o método assíncrono dentro de uma instrução if tivesse a instrução if agindo como uma instrução using.


0

Você não precisa criar um método extra para recursividade ou excluir arquivos dentro da pasta extra. Tudo isso fazendo automaticamente chamando

DirectoryInfo.Delete ();

Detalhes está aqui .

Algo assim funciona muito bem:

  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 como variável para excluir o método, excluirá os sub-arquivos e a sub-pasta com os arquivos também.


-2

Nenhuma das respostas acima funcionou para mim. Parece que o uso do meu próprio aplicativo DirectoryInfono diretório de destino estava fazendo com que ele permanecesse bloqueado.

Forçar a coleta de lixo pareceu resolver o problema, mas não imediatamente. Algumas tentativas para excluir onde necessário.

Observe Directory.Existscomo ele pode desaparecer após uma exceção. Não sei por que a exclusão foi atrasada (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.");

11
-1 Programação por coincidência. Que objeto faz o que quando GC'd? Isso é de alguma forma um bom conselho geral? (Eu acredito em você quando você diz que teve um problema e que você usou este código e que você sente que você não tem um problema agora, mas isso não é apenas o ponto)
Ruben Bartelink

@RubenBartelink Concordo. É um truque. Código vodu que faz algo quando não está claro o que está resolvendo ou como. Eu adoraria uma solução adequada.
Reactgular

11
Meu problema é que tudo o que ele adiciona além do stackoverflow.com/a/14933880/11635 é altamente especulativo. Se eu pudesse, estaria dando um -1 para duplicação e um -1 para especulação / programação por coincidência. A aspersão GC.Collecté a) apenas um mau conselho eb) não é uma causa geral suficientemente comum de diretórios bloqueados para merecer inclusão aqui. Basta escolher um dos outros e não semeiam mais confusão na mente dos leitores inocentes
Ruben Bartelink

3
Use GC.WaitForPendingFinalizers (); após GC.Collect (); isso funcionará conforme o esperado.
Heiner

Não tenho certeza, não foi testado, mas talvez seja melhor fazer algo com uma usingdeclaração: 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

add true no segundo param.

Directory.Delete(path, true);

Isso removerá tudo.


11
Directory.Delete (caminho, verdadeiro); foi a pergunta original
Pisu
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.