我正在使用.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?
编者注:尽管这个答案包含一些有用的信息,但事实上它的工作原理是不正确的Directory.Delete
.请阅读此答案的评论以及此问题的其他答案.
我之前遇到过这个问题.
问题的根源是此函数不会删除目录结构中的文件.因此,您需要做的是创建一个函数,在删除目录本身之前删除目录结构中的所有文件,然后删除所有目录.我知道这与第二个参数相反,但它是一种更安全的方法.此外,您可能希望在删除文件之前从文件中删除READ-ONLY访问属性.否则会引发异常.
只需将此代码打入您的项目即可.
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:\
.
如果你试图以递归方式删除目录a
并且a\b
在资源管理器中打开目录,b
则会被删除但是你会得到错误"目录不为空",a
即使你去看看时它是空的.任何应用程序的当前目录(包括Explorer)都保留目录的句柄.当你打电话时Directory.Delete(true)
,它从下往上删除:b
然后a
.如果b
在资源管理器中打开,资源管理器将检测到删除b
,向上更改目录cd ..
并清理打开的句柄.由于文件系统异步Directory.Delete
操作,操作因与Explorer冲突而失败.
我最初发布了以下解决方案,其中包含中断当前线程以允许Explorer时间释放目录句柄的想法.
// incomplete! try { Directory.Delete(path, true); } catch (IOException) { Thread.Sleep(0); Directory.Delete(path, true); }
但这仅在打开目录是要删除的目录的直接子目录时才有效.如果a\b\c\d
在资源管理器中打开并且您使用此功能a
,则删除d
和后此技术将失败c
.
即使在资源管理器中打开了其中一个较低级别的目录,此方法也将处理深层目录结构的删除.
////// Depth-first recursive delete, with handling for descendant /// directories open in Windows Explorer. /// 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)
在try
块的开头添加a来减少在典型条件下抛出和捕获的异常数.此外,存在一种风险,即在系统负载较重的情况下,您可以通过两次Directory.Delete
尝试而失败.将此解决方案视为更强大的递归删除的起点.
此解决方案仅解决与Windows资源管理器交互的特性.如果你想要一个坚如磐石的删除操作,要记住的一点是,任何东西(病毒扫描程序,无论什么)都可以随时打开你想要删除的内容.所以你必须稍后再试.多久以后,您尝试了多少次,取决于删除对象的重要性.正如MSDN所示,
强大的文件迭代代码必须考虑到文件系统的许多复杂性.
这个无辜的声明,只提供了NTFS参考文档的链接,应该让你的头发站起来.
(编辑:很多.这个答案最初只有第一个不完整的解决方案.)
在进一步研究之前,请检查您控制的以下原因:
文件夹是否设置为进程的当前目录?如果是,请先将其更改为其他内容.
您是否从该文件夹中打开了文件(或加载了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://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 }
在我看来,这样的帮助应该用于所有删除,因为虚假的失败总是可能的.但是,您应该将此代码添加到您的使用案例中,而不是盲目地复制它.
我的应用程序生成的内部数据文件夹的虚假失败,位于%LocalAppData%下,所以我的分析如下:
该文件夹仅由我的应用程序控制,并且用户没有正当理由去将该文件夹中的内容标记为只读或无法访问,因此我不会尝试处理该情况.
那里没有有价值的用户创建的东西,所以不存在强行删除错误的东西的风险.
作为一个内部数据文件夹,我不认为它在资源管理器中是开放的,至少我觉得不需要专门处理这个案例(即我通过支持很好地处理这种情况).
如果所有尝试都失败,我选择忽略该错误.最糟糕的情况是,该应用程序无法解压缩一些较新的资源,崩溃并提示用户联系支持,只要不经常发生,我就可以接受.或者,如果应用程序没有崩溃,它将留下一些旧数据,这也是我可以接受的.
我选择将重试次数限制为500毫秒(50*10).这是一个在实践中起作用的任意门槛; 我希望阈值足够短,以便用户不会杀死应用程序,认为它已经停止响应.另一方面,半秒钟是罪犯完成处理我的文件夹的充足时间.从其他有时甚至Sleep(0)
可以接受的SO答案来看,很少有用户会经历多次重试.
我每50ms重试一次,这是另一个任意数字.我觉得如果一个文件正在处理(索引,检查),当我尝试删除它时,50ms是在我的情况下期望完成处理的正确时间.此外,50ms足够小,不会导致明显的减速; 再次,Sleep(0)
在许多情况下似乎已经足够了,所以我们不想拖延太多.
代码重试任何IO异常.我通常不希望任何异常访问%LocalAppData%,因此我选择简单并接受500毫秒延迟的风险,以防发生合法异常.我也不想找到一种方法来检测我想要重试的确切异常.
应该提到的一件重要事情(我将其添加为注释,但我不允许)是重载的行为从.NET 3.5更改为.NET 4.0.
Directory.Delete(myPath, true);
从.NET 4.0开始,它删除文件夹本身但不在3.5中的文件.这也可以在MSDN文档中看到.
.NET 4.0
删除指定的目录,如果指示,则删除目录中的所有子目录和文件.
.NET 3.5
删除空目录,如果指示,则删除目录中的所有子目录和文件.
我在Delphi下遇到了同样的问题.最终的结果是我自己的应用程序锁定了我想要删除的目录.不知何故,当我写入目录时,目录被锁定(一些临时文件).
捕获22是,我在删除它之前为它的父进行了一个简单的更改目录.
接受的答案是完全错误的,它可能适用于某些人,因为从磁盘获取文件所花费的时间可以释放锁定文件的任何内容.事实是,这是因为文件被某些其他进程/流/操作锁定.其他答案使用Thread.Sleep
(Yuck)在一段时间后重试删除目录.这个问题需要重新审视一个更现代的答案.
public static async Task单元测试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); Tasktask; 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); } } }
我很惊讶没有人想到这个简单的非递归方法,它可以删除包含只读文件的目录,而无需更改每个文件的只读属性.
Process.Start("cmd.exe", "/c " + @"rmdir /s/q C:\Test\TestDirectoryContainingReadOnlyFiles");
(稍微更改一下,不要暂时触发cmd窗口,这可以通过互联网获得)
您可以通过运行以下方式重现错误:
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"目录不为空".这是愚蠢的,因为我们刚刚删除了目录'c'.
根据我的理解,解释是目录'c'被标记为已删除.但删除尚未在系统中提交.系统已完成作业的回复,而实际上它仍在处理中.系统可能正在等待文件资源管理器将焦点放在父目录上以提交删除.
如果您查看删除功能的源代码(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的解决方案.
我有一个奇怪的权限问题删除用户配置文件目录(在C:\ Documents and Settings中),尽管能够在Explorer shell中这样做.
File.SetAttributes(target_dir, FileAttributes.Normal); Directory.Delete(target_dir, false);
对我来说,"文件"操作对目录的作用没有任何意义,但我知道它有效,这对我来说已经足够了!