如果您尝试递归删除目录a
并且目录a\b
在资源管理器中打开,b
将被删除,但您会收到错误“目录不是空的”,a
即使您去查看时它是空的。任何应用程序(包括资源管理器)的当前目录都保留了该目录的句柄。当您调用 时Directory.Delete(true)
,它会从下往上删除:b
,然后a
。如果b
在资源管理器中打开,资源管理器将检测删除b
,向上更改目录cd ..
并清理打开的句柄。由于文件系统是异步操作的,所以Directory.Delete
操作会因为与资源管理器的冲突而失败。
不完整的解决方案
我最初发布了以下解决方案,其想法是中断当前线程以允许资源管理器有时间释放目录句柄。
// incomplete!
try
{
Directory.Delete(path, true);
}
catch (IOException)
{
Thread.Sleep(0);
Directory.Delete(path, true);
}
但这仅在打开目录是您要删除的目录的直接子目录时才有效。如果a\b\c\d
在资源管理器中打开并且您在 上使用它,则此技术在删除和a
后将失败。d
c
一个更好的解决方案
即使在资源管理器中打开了较低级别的目录之一,此方法也将处理深层目录结构的删除。
/// <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
此过程中可能发生的事情。目前尚不清楚第一次删除尝试是否为第二次成功的删除尝试铺平了道路,或者仅仅是抛出/捕获允许文件系统赶上的异常所引入的时间延迟。
您可以通过Thread.Sleep(0)
在块的开头添加 a 来减少在典型条件下引发和捕获的异常数量try
。此外,在繁重的系统负载下,您可能会经历两次Directory.Delete
尝试并失败。将此解决方案视为更强大的递归删除的起点。
一般回答
此解决方案仅解决与 Windows 资源管理器交互的特殊性。如果您想要一个坚如磐石的删除操作,要记住的一件事是,任何东西(病毒扫描程序等)都可以随时对您要删除的内容有一个开放的句柄。所以你必须稍后再试。多久之后,以及尝试多少次,取决于删除对象的重要性。正如MSDN 指出的那样,
健壮的文件迭代代码必须考虑文件系统的许多复杂性。
这个无辜的声明,只提供了一个指向 NTFS 参考文档的链接,应该会让你毛骨悚然。
(编辑:很多。这个答案最初只有第一个不完整的解决方案。)