无法使用 Directory.Delete(path, true) 删除目录

Posted

技术标签:

【中文标题】无法使用 Directory.Delete(path, true) 删除目录【英文标题】:Cannot delete directory with Directory.Delete(path, true) 【发布时间】:2010-09-24 16:01:58 【问题描述】:

我正在使用 .NET 3.5,尝试使用以下方法递归删除目录:

Directory.Delete(myPath, true);

我的理解是,如果文件正在使用或存在权限问题,这应该抛出,否则它应该删除目录及其所有内容。

但是,我偶尔会得到这个:

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

我对这个方法有时会抛出并不感到惊讶,但是当递归为真时,我很惊讶得到这个特定的消息。 (我知道目录不是空的。)

我看到这个而不是 AccessViolationException 有什么原因吗?

【问题讨论】:

您不会看到 AccessViolationException -- 这是针对无效指针操作的,而不是针对磁盘访问的。 这似乎确实是某种 IO 问题,而不仅仅是目录不为空,例如打开的文件句柄之类的。我会尝试使用递归删除选项,然后在捕获 IOException 时,搜索并关闭任何打开的文件句柄,然后重试。这里有一个讨论:***.com/questions/177146/… 【参考方案1】:

编者注:虽然这个答案包含一些有用的信息,但实际上它对Directory.Delete 的工作原理是不正确的。请阅读 cmets 以获取此答案以及此问题的其他答案。


我之前遇到过这个问题。

问题的根源在于该函数不会删除目录结构中的文件。所以你需要做的是创建一个函数,在删除目录本身之前删除目录结构中的所有文件,然后是所有目录。我知道这违背了第二个参数,但这是一种更安全的方法。此外,您可能希望在删除文件之前从文件中删除只读访问属性。否则会引发异常。

只需将此代码添加到您的项目中即可。

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

另外,我个人对机器允许删除的区域添加了限制,因为您希望有人在C:\WINDOWS (%WinDir%)C:\ 上调用此功能。

【讨论】:

这是没有意义的。 Directory.Delete(myPath, true) 是删除目录结构中所有文件的重载。如果你想弄错,就弄错 Ryan S 的答案。 +1 因为虽然 Directory.Delete() 确实删除了其子目录中的文件(递归 = true),但如果其中一个子目录或文件,它会抛出“IOException:目录不为空”是只读的。所以这个解决方案比 Directory.Delete() 效果更好 您关于Directory.Delete(path, true) 不删除文件的说法是错误的。见 MSDN msdn.microsoft.com/en-us/library/fxeahc5f.aspx -1 有人可以明确指出这种方法的有效性非常值得怀疑。如果Directory.Delete(string,bool) 失败,则表示某些东西被锁定或被误用,并且没有一种适合所有此类问题的解决方案。人们需要在他们的上下文中解决这个问题,我们不应该对这个问题的每一个想法都大发雷霆(重试和吞咽异常)并希望得到一个好的结果。 如果您要删除的目录有指向其他文件夹的快捷方式/符号链接,请注意这种方法 - 您最终可能会删除比预期更多的内容【参考方案2】:

如果您尝试递归删除目录a 并且目录a\b 在资源管理器中打开,b 将被删除,但您将收到a 的错误“目录不为空”,即使它是你去看看的时候是空的。任何应用程序(包括资源管理器)的当前目录retains a handle to the directory。当您调用Directory.Delete(true) 时,它会自下而上删除:b,然后是a。如果b 在资源管理器中打开,资源管理器将检测b 的删除,向上更改目录cd .. 并清理打开的句柄。由于文件系统是异步操作的,Directory.Delete操作会因为与Explorer冲突而失败。

不完整的解决方案

我最初发布了以下解决方案,其想法是中断当前线程以允许资源管理器有时间释放目录句柄。

// incomplete!
try

    Directory.Delete(path, true);

catch (IOException)

    Thread.Sleep(0);
    Directory.Delete(path, true);

但这只有在打开的目录是您要删除的目录的立即子目录时才有效。如果 a\b\c\d 在资源管理器中打开并且您在 a 上使用它,则在删除 dc 后此技术将失败。

更好的解决方案

即使在资源管理器中打开了较低级别的目录之一,此方法也会处理深层目录结构的删除。

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

尽管我们自己进行了额外的递归工作,但我们仍然必须处理可能在此过程中发生的UnauthorizedAccessException。目前尚不清楚第一次删除尝试是否为第二次成功的删除尝试铺平了道路,还是仅仅是由于引发/捕获异常而导致文件系统赶上的时间延迟。

您可以通过在try 块的开头添加Thread.Sleep(0) 来减少在典型情况下引发和捕获的异常数量。此外,在繁重的系统负载下,您可能会飞过两次Directory.Delete 尝试并失败。将此解决方案视为更强大的递归删除的起点。

一般回答

此解决方案仅解决与 Windows 资源管理器交互的特殊性。如果您想要一个坚如磐石的删除操作,要记住的一件事是,任何东西(病毒扫描程序等)都可以随时对您要删除的内容有一个开放的句柄。所以你必须稍后再试。多久之后,以及尝试多少次,取决于删除对象的重要性。作为MSDN indicates,

健壮的文件迭代代码必须考虑许多复杂性 文件系统。

这个无辜的声明,只提供了一个指向 NTFS 参考文档的链接,应该会让你毛骨悚然。

编辑:很多。这个答案最初只有第一个不完整的解决方案。)

【讨论】:

当路径或路径下的文件夹/文件之一在 Windows 资源管理器中打开或选择时,它确实会出现调用 Directory.Delete(path, true) 将引发 IOException。关闭 Windows 资源管理器并重新运行我现有的代码而不使用上面建议的 try/catch。 我无法理解它的工作原理和原因,但它对我有用,而设置文件属性和编写我自己的递归函数却没有。 @CarlosLiu 因为它给了“Explorer 一个释放目录句柄的机会” 系统要求资源管理器“释放目录句柄”,然后尝试删除目录。如果没有及时删除目录句柄,则会引发异常并执行catch 块(同时,资源管理器仍在释放目录,因为没有发送命令告诉它不要这样做)。对Thread.Sleep(0) 的调用可能是必要的,也可能是不必要的,因为catch 块已经给了系统更多的时间,但它确实以低成本提供了一点额外的安全性。之后,Delete 被调用,目录已经被释放。 @PandaWood 实际上只有这个 Sleep(100) 对我有用。睡眠(0)不起作用。我不知道发生了什么以及如何正确解决这个问题。我的意思是,如果它取决于服务器负载并且将来应该有 300 或 400 怎么办?这怎么知道。必须是另一种正确的方式...【参考方案3】:

在继续之前,请检查您可以控制的以下原因:

文件夹是否设置为进程的当前目录?如果是,请先将其更改为其他内容。 您是否从该文件夹中打开了一个文件(或加载了一个 DLL)? (忘记关闭/卸载它)

否则,请检查您无法控制的以下合法原因:

该文件夹中有标记为只读的文件。 您没有删除其中某些文件的权限。 文件或子文件夹已在资源管理器或其他应用中打开。

如果出现上述任何问题,您应该在尝试改进删除代码之前了解它发生的原因。 应该您的应用删除只读或不可访问的文件吗?谁用这种方式标记它们,为什么?

排除上述原因后,仍有可能出现虚假故障。如果有人持有正在删除的任何文件或文件夹的句柄,则删除将失败,并且有人可能会枚举文件夹或读取其文件的原因有很多:

搜索索引器 防病毒软件 备份软件

处理虚假失败的一般方法是多次尝试,在尝试之间暂停。你显然不想永远尝试,所以你应该在尝试一定次数后放弃,要么抛出异常,要么忽略错误。像这样:

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://***.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

在我看来,所有删除都应该使用这样的助手,因为虚假失败总是可能的。但是,您应该根据您的用例调整此代码,而不是盲目地复制它。

我的应用程序生成的内部数据文件夹出现虚假故障,位于 %LocalAppData% 下,因此我的分析如下:

    该文件夹完全由我的应用程序控制,用户没有正当理由去将该文件夹内的内容标记为只读或不可访问,因此我不会尝试处理这种情况。

    其中没有有价值的用户创建的内容,因此不存在错误强行删除内容的风险。

    作为一个内部数据文件夹,我不希望它在资源管理器中打开,至少我觉得不需要专门处理这个案例(即我可以通过支持处理那个案例) .

    如果所有尝试都失败,我选择忽略错误。最坏的情况是,应用程序无法解压一些较新的资源,崩溃并提示用户联系支持人员,这对我来说是可以接受的,只要它不经常发生。或者,如果应用没有崩溃,它会留下一些旧数据,这也是我可以接受的。

    我选择将重试次数限制为 500 毫秒 (50 * 10)。这是一个在实践中有效的任意阈值;我希望阈值足够短,这样用户就不会认为应用程序已停止响应而终止应用程序。另一方面,半秒对于罪犯来说是足够的时间来完成我的文件夹的处理。从有时甚至可以接受Sleep(0) 的其他 SO 答案来看,很少有用户会经历一次以上的重试。

    我每 50 毫秒重试一次,这是另一个任意数字。我觉得如果在我尝试删除文件时正在处理(索引、检查)文件,那么 50ms 大约是预期处理在我的情况下完成的正确时间。此外,50ms 足够小,不会导致明显的减速;再说一遍,Sleep(0) 在很多情况下似乎已经足够了,所以我们不想耽误太多。

    代码重试任何 IO 异常。我通常不会期望任何异常访问 %LocalAppData%,因此我选择了简单并接受了在发生合法异常时延迟 500 毫秒的风险。我也不想找到一种方法来检测我想要重试的确切异常。

【讨论】:

P.P.S.几个月后,我很高兴地报告,这段(有点疯狂的)代码已经完全解决了这个问题。有关此问题的支持请求已降至零(大约每周 1-2 个)。 +0 虽然这是一个更健壮且更少的“这里”;比***.com/a/7518831/11635 更适合您的完美解决方案,对我来说同样适用 - 巧合编程 - 小心处理。您的代码中体现的一个有用的一点是,如果您要重试,您确实需要考虑到您正在与自上次尝试以来目录是否“消失”的模糊性竞争[和一个幼稚的 @987654325 @guard 不会解决这个问题。] @RubenBartelink 虽然这段代码非常随机(而且 500 毫秒的延迟可能还不够),但我真的没有看到任何理智的方法来处理不允许删除打开项目的文件系统.它不像 Windows 有一个 API 来获得对文件夹的独占访问权限。任何在实践中有效、不会产生支持问题并且具有合理的最坏情况行为的解决方案在我的书中都是可以接受的。 @RubenBartelink 好的,所以我认为我们可以同意这一点:发布一段适用于一个特定应用程序的代码(并且从未意味着适合每种情况)作为一个 SO 答案对许多新手和/或无知的开发人员造成伤害。我把它作为定制的起点,但是,是的,有些人会按原样使用它,这是一件坏事。 @nopara 你不需要比较;如果我们脱离了循环,我们就失败了。是的,在许多情况下,您会想要抛出异常,然后在堆栈中添加适当的错误处理代码,可能带有用户可见的消息。【参考方案4】:

现代异步应答

接受的答案是完全错误的,它可能对某些人有用,因为从磁盘获取文件所花费的时间释放了锁定文件的任何东西。事实是,发生这种情况是因为文件被其他一些进程/流/动作锁定。其他答案使用Thread.Sleep(Yuck)在一段时间后重试删除目录。这个问题需要用更现代的答案重新审视。

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;

单元测试

这些测试显示了锁定文件如何导致Directory.Delete 失败以及上述TryDeleteDirectory 方法如何解决问题的示例。

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

【讨论】:

您能否详细说明“现代”的含义?你的方法有什么好处?在您看来,为什么其他人是错误的? 其他没有错。他们只是使用像 Thread.Sleep 这样的旧 API,您今天应该避免使用 async/awaitTask.Delay。这是可以理解的,这是一个非常古老的问题。 由于BC36943 'Await' cannot be used inside a 'Catch' statement, a 'Finally' statement, or a 'SyncLock' statement.,这种方法在 VB.Net 中不起作用(至少不是非常文字的逐行转换) @amonroejj 您必须使用旧版本。这是固定的。 小改进而不是 return true if (!Directory.Exists(directoryPath)) return true; await Task.Delay(millisecondsDelay); 等到目录真的消失了【参考方案5】:

应该提到的一件重要的事情(我将它作为评论添加,但我不允许这样做)是重载的行为从 .NET 3.5 更改为 .NET 4.0。

Directory.Delete(myPath, true);

从 .NET 4.0 开始,它会删除文件夹本身中的文件,但不会删除 3.5 中的文件。这也可以在 MSDN 文档中看到。

.NET 4.0

删除指定的目录,以及目录中的所有子目录和文件(如果有的话)。

.NET 3.5

删除一个空目录,以及目录中的所有子目录和文件(如果有的话)。

【讨论】:

我认为这只是一个文档更改......如果它只删除一个“空目录”,那么使用 2° 参数删除目录中的文件意味着什么?如果为空则没有文件... 恐怕你猜错了。在使用两个框架版本测试代码后,我已经发布了这个。在 3.5 中删除非空文件夹会抛出异常。【参考方案6】:

我在 Delphi 下遇到了同样的问题。最终结果是我自己的应用程序锁定了我要删除的目录。当我写入目录时,不知何故该目录被锁定(一些临时文件)。

第 22 个问题是,我在删除它之前对它的父级做了一个简单的更改目录

【讨论】:

+1 现在msdn for Directory.Delete 确实提到了一些事情! 任何带有完整源代码示例的最终解决方案?【参考方案7】:

你可以通过运行来重现错误:

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

当试图删除目录'b'时,它会抛出 IOException "The directory is not empty"。这很愚蠢,因为我们刚刚删除了目录“c”。

据我了解,解释是目录“c”被标记为已删除。但删除尚未在系统中提交。系统已回复作业已完成,实际上仍在处理中。系统可能会等待文件资源管理器关注父目录来提交删除。

如果您查看 Delete 函数 (http://referencesource.microsoft.com/#mscorlib/system/io/directory.cs) 的源代码,您会发现它使用了本机 Win32Native.RemoveDirectory 函数。此处记录了这种不要等待的行为:

RemoveDirectory 函数将目录标记为在关闭时删除。因此,在关闭该目录的最后一个句柄之前,不会删除该目录。

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

睡眠并重试是解决方案。参考 ryascl 的解决方案。

【讨论】:

【参考方案8】:

我很惊讶没有人想到这种简单的非递归方法,它可以删除包含只读文件的目录,而无需更改每个目录的只读属性。

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

(稍微改变一下,不要立即触发 cmd 窗口,这在整个互联网上都可用)

【讨论】:

很高兴与我们分享,但您能否提供一些更改以防止触发 cmd 窗口,而不是提示我们在网上搜索它? 这不起作用。在我可以从命令提示符或资源管理器中删除文件的相同情况下,使用此代码调用 rmdir 会给出退出代码 145,它转换为“目录不为空”。它使目录为空但仍然在原处,就像 Directory.Delete("", true) @Kevin Coulombe,嗯……你确定你在使用 /s/q 开关吗? @KevinCoulombe:是的,一定是那些 COM 组件。当我尝试使用普通的旧 C# 时,它可以工作,并且确实会删除目录以及其中的文件(只读或非只读文件)。 如果您开始依赖外部组件来实现框架中应该包含的内容,那么这是一个“不太理想”的想法,因为它不再可移植(或更难)。如果exe不存在怎么办?还是 /option 改变了?如果 Jeremy Edwards 的解决方案有效,那么它应该是首选恕我直言【参考方案9】:

尽管可以在 Explorer shell 中删除用户配置文件目录(在 C:\Documents 和设置中),但我遇到了一些奇怪的权限问题。

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

“文件”操作对目录的作用对我来说毫无意义,但我知道它可以工作,这对我来说已经足够了!

【讨论】:

当目录有很多文件并且资源管理器正在打开包含这些文件的文件夹时,仍然没有希望。【参考方案10】:

不删除文件的递归目录删除肯定是出乎意料的。我的解决方法:

public class IOUtils

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

我遇到过这样的情况,但通常,Directory.Delete 会在递归删除时删除目录内的文件,如documented in msdn。

作为 Windows 资源管理器的用户,我有时也会遇到这种不正常的行为:有时我无法删除文件夹(它认为无意义的消息是“访问被拒绝”)但是当我向下钻取并删除较低的项目时,我可以然后也删除上面的项目。所以我猜上面的代码处理的是操作系统异常——而不是基类库问题。

【讨论】:

谢谢。这很有帮助,但它仍然无法处理 Delete() 引发的异常。 是的。更困难的情况需要更严格的措施 - 例如,如果文件被锁定,则可能会发出警报、重试等等。这只是一个修复 - 可能是常见的 - 案例。【参考方案11】:

此答案基于:https://***.com/a/1703799/184528。与我的代码不同的是,我们只在必要时递归许多删除子目录和文件,第一次尝试调用 Directory.Delete 失败(这可能是由于 Windows 资源管理器查看目录而发生的)。

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

【讨论】:

如果有UnauthorizedAccessException,应该如何删除文件夹?它会再次抛出。然后再次。再一次......因为每次它都会去catch并再次调用该函数。 Thread.Sleep(0); 不会更改您的权限。此时,它应该只记录错误并优雅地失败。只要(子)目录打开,这个循环就会继续 - 它不会以编程方式关闭它。只要这些东西保持开放,我们是否准备让它这样做?有没有更好的办法? 如果有UnauthorizedAccessException,它将手动尝试手动删除每个文件。所以它通过遍历目录结构继续取得进展。是的,可能每个文件和目录都会抛出相同的异常,但这也可能仅仅因为资源管理器持有它的句柄而发生(请参阅***.com/a/1703799/184528)我会将“tryAgain”更改为“secondTry”以使其更清晰。 为了更简洁地回答,它传递“true”并执行不同的代码路径。 对,看到你的编辑,但我的意思不是删除文件,而是删除目录。我写了一些代码,基本上可以在Process.Kill() 对文件可能被锁定的任何进程执行并删除文件。我遇到的问题是删除其中一个文件仍处于打开状态的目录时(请参阅***.com/questions/41841590/…)。所以回到这个循环,不管它在做什么,如果它再次在该文件夹上执行Directory.Delete(),如果无法释放该句柄,它仍然会失败。 UnauthorizedAccessException 也会发生同样的情况,因为删除文件(假设这甚至是允许的,因为要访问该代码,它在 Directory.Delete() 上失败)不会神奇地授予您删除文件的权限目录。【参考方案12】:

以上解决方案都不适用于我。我最终使用了@ryascl 解决方案的编辑版本,如下所示:

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

【讨论】:

【参考方案13】:

您是否可能存在竞争条件,即另一个线程或进程正在向目录添加文件:

顺序是:

删除进程A:

    清空目录 删除(现在为空)目录。

如果其他人在 1 和 2 之间添加了一个文件,那么 2 可能会抛出列出的异常?

【讨论】:

【参考方案14】:

我花了几个小时来解决这个问题和删除目录的其他异常。 这是我的解决方案

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

此代码延迟很小,这对我的应用程序并不重要。但要小心,如果您要删除的目录中有很多子目录,延迟可能会给您带来问题。

【讨论】:

-1 延迟是怎么回事?请不要碰巧编程! @Ruben 我没有说你错了。我刚刚说过,仅仅为了这个而否决它是一种严厉的惩罚。我同意你的看法,但是,4 次赞成并没有导致 4 次反对。我也会赞成您的评论,但由于无法解释的延迟,我不会反对答案:) @RubenBartelink 和其他人:虽然我并不特别喜欢这段代码(我已经发布了另一个采用类似方法的解决方案),但这里的延迟是合理的。该问题很可能不在应用程序的控制范围内;也许另一个应用程序会定期重新扫描 FS,从而在短时间内锁定文件夹。延迟解决了问题,将错误报告计数减至零。谁在乎我们是否对根本原因一无所知? @RubenBartelink 其实仔细想想,not在NTFS目录删除过程中使用delay-and-retry的方式是不负责任的解决方案。任何一种正在进行的文件遍历都会阻止删除,因此它迟早会失败。而且您不能指望所有第三方搜索、备份、防病毒和文件管理工具都不会出现在您的文件夹中。 @RubenBartelink 另一个例子,假设你给了 100ms 的延迟,目标 PC 上任何软件的最高锁定时间是 AV 软件=90ms。假设它还具有将文件锁定 70 毫秒的备份软件。现在 AV 锁定了一个文件,您的应用程序等待 100 毫秒,这通常很好,但随后遇到另一个锁定,因为备份软件在 AV 扫描的 70 毫秒标记处开始抓取文件,因此需要另外 40 毫秒来释放文件。因此,虽然 AV 软件需要更长的时间并且您的 100 毫秒通常比这两个应用程序中的任何一个都长,但您仍然需要考虑它何时启动。【参考方案15】:

您不必创建额外的递归方法或删除文件夹内的文件。这一切都是通过调用自动完成的

DirectoryInfo.Delete();

详情是here。

这样的东西效果很好:

  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
    

将true作为变量传递给delete方法,也会删除子文件和带有文件的子文件夹。

【讨论】:

除了它有时会抛出 IO 异常。【参考方案16】:

目录或其中的文件已锁定,无法删除。找出锁定它的罪魁祸首,看看你能不能消除它。

【讨论】:

T1000 to user-with-folder-open:“你被终止了!”【参考方案17】:

在 Windows 资源管理器中选择路径或子文件夹似乎足以阻止 Directory.Delete(path, true) 的单次执行,如上所述抛出 IOException 并死掉而不是将 Windows 资源管理器引导到父文件夹并按预期进行。

【讨论】:

这似乎是我的问题。一旦我关闭资源管理器并再次运行,也不例外。即使选择父母的父母也是不够的。我不得不关闭 Explorer。 是的,这种情况发生并且是一个原因。那么知道如何以编程方式处理它,或者答案是否总是确保所有 1000 个用户都关闭该文件夹?【参考方案18】:

我今天遇到了这个问题。发生这种情况是因为我将 Windows 资源管理器打开到试图被删除的目录,导致递归调用失败,从而导致 IOException。确保没有打开该目录的句柄。

另外,MSDN 很清楚,你不必自己写recusion:http://msdn.microsoft.com/en-us/library/fxeahc5f.aspx

【讨论】:

【参考方案19】:

我在使用 TFS2012 的构建服务器上使用 Windows Workflow Foundation 时遇到了同样的问题。在内部,名为 Directory.Delete() 的工作流将递归标志设置为 true。在我们的案例中,它似乎与网络相关。

我们在使用最新的二进制文件重新创建和重新填充之前删除了网络共享上的二进制放置文件夹。其他所有构建都会失败。在构建失败后打开放置文件夹时,该文件夹为空,这表明 Directory.Delete() 调用的所有方面都成功,除了删除实际目录。

该问题似乎是由网络文件通信的异步特性引起的。构建服务器告诉文件服务器删除所有文件,文件服务器报告它已经删除,即使它还没有完全完成。然后构建服务器请求删除目录,文件服务器拒绝了该请求,因为它还没有完全删除文件。

在我们的案例中,有两种可能的解决方案:

在我们自己的代码中建立递归删除,每个步骤之间都有延迟和验证 在出现 IOException 后最多重试 X 次,在重试之前会产生延迟

后一种方法快速而肮脏,但似乎可以解决问题。

【讨论】:

【参考方案20】:

这是因为 FileChangesNotifications.

它发生在 ASP.NET 2.0 之后。当您删除应用中的某个文件夹时,它会重新启动。您可以自己查看,使用 ASP.NET Health Monitoring.

只需将此代码添加到您的 web.config/configuration/system.web:

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

之后查看Windows Log -&gt; Application。 到底是怎么回事:

删除文件夹时,如果有子文件夹,Delete(path, true)会先删除子文件夹。 FileChangesMonitor 知道删除和关闭您的应用程序就足够了。同时你的主目录还没有被删除。这是来自 Log 的事件:

Delete() 没有完成它的工作,因为应用程序正在关闭,它引发了一个异常:

当您在要删除的文件夹中没有任何子文件夹时,Delete() 只会删除所有文件和该文件夹,应用程序也会重新启动,但您不会得到任何例外,因为应用重启不会中断任何事情。但是,您仍然会丢失所有进程内会话,应用程序在重新启动时不响应请求等。

现在呢?

有一些解决方法和调整可以禁用此行为,Directory Junction、Turning Off FCN with Registry、Stopping FileChangesMonitor using Reflection (since there is no exposed method),但它们似乎都不正确,因为 FCN 的存在是有原因的。它负责您的应用程序的结构,而不是您的数据结构。简短的回答是:将要删除的文件夹放在应用程序之外。 FileChangesMonitor 不会收到任何通知,并且您的应用程序不会每次都重新启动。你不会有任何例外。要让它们在网络上可见,有两种方法:

    制作一个控制器来处理来电,然后通过从应用程序外部(wwwroot 外部)的文件夹中读取来返回文件。

    如果您的项目很大并且性能最重要,请设置单独的小型快速网络服务器来提供静态内容。因此,您将把他的具体工作留给 IIS。它可以在同一台机器上(Windows 的猫鼬)或另一台机器(Linux 的 nginx)。好消息是您无需支付额外的 microsoft 许可证即可在 linux 上设置静态内容服务器。

希望这会有所帮助。

【讨论】:

【参考方案21】:

当目录(或任何子目录)中存在路径长度大于 260 个符号的文件时,此问题会出现在 Windows 上。

在这种情况下,您需要删除 \\\\?\C:\mydir 而不是 C:\mydir。 关于 260 个符号的限制,您可以阅读 here。

【讨论】:

【参考方案22】:

我已经用这个千年的技术解决了(你可以让 Thread.Sleep 自己留在陷阱中)

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

【讨论】:

你是说这种方法每千年才有效一次?【参考方案23】:

如上所述,“已接受”解决方案在重解析点上失败。 有一个更短的解决方案可以正确复制功能:

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

我知道,不处理后面提到的权限情况,但出于所有意图和目的,FAR BETTER 提供了原始/库存 Directory.Delete() 预期功能 - 和代码也少了很多

您可以安全地继续处理,因为旧目录将不碍事...即使没有消失,因为“文件系统仍在追赶”(或任何 MS 提供的提供损坏功能的借口).

作为一个好处,如果您知道您的目标目录很大/很深并且不想等待(或因异常而烦恼),最后一行可以替换为:

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

您仍然可以安全地继续工作。

【讨论】:

你的分配可以简化为:string tfilename = Path.Combine(Path.GetDirectoryName(target), Path.GetRandomFileName()); 我必须同意皮特的观点。编写的代码不会添加分隔符。它采用了我的\\server\C$\dir 路径并使其成为\\server\C$asf.yuw。结果我在Directory.Move() 上遇到了一个错误——Source and destination path must have identical roots. Move will not work across volumes. 工作正常,一旦我使用了 Pete 的代码,除了没有处理锁定文件或打开目录时的句柄 - 所以它永远不会到达 ThreadPool 命令。跨度> 注意:此答案只能与 recursive=true 一起使用。当为 false 时,即使目录不为空,也会移动目录。这将是一个错误;在这种情况下,正确的行为是抛出异常,并保持目录不变。【参考方案24】:

如果您的应用程序(或任何其他应用程序)的当前目录是您要删除的目录,则它不会是访问冲突错误,但目录不为空。 通过更改当前目录确保它不是您自己的应用程序;此外,请确保该目录未在其他程序中打开(例如 Word、excel、Total Commander 等)。大多数程序会 cd 到最后打开的文件所在的目录,这会导致这种情况。

【讨论】:

【参考方案25】:

如果是网络文件,Directory.DeleteHelper(recursive:=true) 可能会导致删除文件延迟导致的IOException

【讨论】:

【参考方案26】:

当方法异步并像这样编码时,我解决了上述问题的一个可能实例:

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

有了这个:

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

结论?摆脱用于检查存在的句柄的一些异步方面是微软无法与之交谈的。就好像 if 语句中的异步方法具有 if 语句的作用类似于 using 语句。

【讨论】:

【参考方案27】:

以上答案都不适合我。看来我自己的应用在目标目录上使用DirectoryInfo 导致它保持锁定状态。

强制垃圾回收似乎可以解决问题,但不是马上。在需要的地方尝试删除。

请注意Directory.Exists,因为它可能会在出现异常后消失。我不知道为什么我的删除被延迟了(Windows 7 SP1)

        for (int attempts = 0; attempts < 10; attempts++)
        
            try
            
                if (Directory.Exists(folder))
                
                    Directory.Delete(folder, true);
                
                return;
            
            catch (IOException e)
            
                GC.Collect();
                Thread.Sleep(1000);
            
        

        throw new Exception("Failed to remove folder.");

【讨论】:

-1 编程巧合。什么对象在 GC 时做什么?这在任何方面都是好的一般建议吗? (当你说你有问题并且你使用了这段代码并且你觉得你现在没有问题时,我相信你,但这不是重点) @RubenBartelink 我同意。这是一个黑客。当不清楚它正在解决什么或如何解决时,Voodoo 代码会做某事。我想要一个合适的解决方案。 我的问题是它在***.com/a/14933880/11635 之外添加的任何内容都是高度推测性的。如果可以的话,我会巧合地给出一个-1 表示重复和一个-1 表示推测/编程。洒GC.Collect 是a)只是不好的建议和b)不是一个足够常见的锁定目录的一般原因,值得包含在此处。随便挑一个,不要在无辜的读者心中造成更多的困惑 使用 GC.WaitForPendingFinalizers(); GC.Collect() 之后;这将按预期工作。 不确定,未经测试,但也许更好的是使用using 声明,然后:using (DirectoryInfo di = new DirectoryInfo(@"c:\MyDir")) for (int attempts = 0; attempts &lt; 10; attempts++) try if (di.Exists(folder)) Directory.Delete(folder, true); return; catch (IOException e) Thread.Sleep(1000);

以上是关于无法使用 Directory.Delete(path, true) 删除目录的主要内容,如果未能解决你的问题,请参考以下文章

为啥我使用 directory.delete 时 gif 文件不会删除?

Directory.Delete(folder) 和 Directory.Delete(folder, false) 有啥区别?

在Directory.Delete Directory.Exists有时返回true?

为啥 System.IO.Directory.Delete(string,bool) 仅在 asp.net 站点模拟用户时将文件夹标记为已删除

32.Directory类

32.Directory类