我正在使用.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?
29 回答
在网络文件的情况下,Directory.DeleteHelper(recursive:= true)可能导致由于删除文件的延迟而导致的IOException
我认为有一个文件打开你不知道我遇到了同样的问题并通过关闭所有指向我想要删除的目录的流来解决它 .
如果考虑使用任何文件或目录,则会发生此错误 . 这是一个误导性的错误 . 检查是否有任何资源管理器窗口或命令行窗口打开到树中的任何目录,或者是否在该树中使用文件的程序 .
Editor's note: 虽然这个答案包含一些有用的信息,但事实上
Directory.Delete
的工作方式是不正确的 . 请阅读此答案的评论以及此问题的其他答案 .我以前遇到过这个问题 .
问题的根源是此函数不会删除目录结构中的文件 . 因此,您需要做的是创建一个函数,在删除目录本身之前删除目录结构中的所有文件,然后删除所有目录 . 我知道这违背了第二个参数,但它是一种更安全的方法 . 此外,您可能希望在删除文件之前从文件中删除READ-ONLY访问属性 . 否则会引发异常 .
只需将此代码打入您的项目即可 .
此外,对我来说,我个人添加了对允许删除的机器区域的限制,因为您是否希望有人在
C:\WINDOWS (%WinDir%)
或C:\
上调用此功能 .如果您尝试递归删除目录
a
并且目录a\b
在资源管理器中打开,b
将被删除,但是当您去看时,即使它是空的,您也会收到'directory is not empty'的错误a
. 任何应用程序的当前目录(包括资源管理器)retains a handle to the directory . 当你调用Directory.Delete(true)
时,它会从下往上删除:b
,然后是a
. 如果在资源管理器中打开b
,资源管理器将检测到b
的删除,向上更改目录cd ..
并清理打开的句柄 . 由于文件系统异步操作,Directory.Delete
操作因与Explorer冲突而失败 .不完整的解决方案
我最初发布了以下解决方案,其中包含中断当前线程以允许Explorer时间释放目录句柄的想法 .
但这仅在打开目录是要删除的目录的直接子目录时才有效 . 如果在资源管理器中打开
a\b\c\d
并在a
上使用此项,则删除d
和c
后此技术将失败 .一个更好的解决方案
即使在资源管理器中打开了其中一个较低级别的目录,此方法也将处理深层目录结构的删除 .
尽管我们自己进行了额外的递归工作,但我们仍然必须处理可能发生的
UnauthorizedAccessException
. 它只是抛出/捕获允许文件系统赶上的异常引入的定时延迟 .您可以通过在
try
块的开头添加Thread.Sleep(0)
来减少在典型条件下抛出和捕获的异常数 . 此外,存在这样的风险:在系统负载较重的情况下,您可以通过两次_948308尝试并尝试失败 . 将此解决方案视为更强大的递归删除的起点 .一般答案
此解决方案仅解决与Windows资源管理器交互的特性 . 如果你想要一个坚如磐石的删除操作,要记住的一件事是任何东西(病毒扫描程序,无论如何)都可以随时打开你想要删除的内容 . 所以你必须稍后再试 . 多久以后,您尝试多少次,取决于删除对象的重要性 . 如MSDN indicates,
这个无辜的声明,只提供了NTFS参考文档的链接,应该让你的头发站起来 .
( Edit :很多 . 这个答案最初只有第一个不完整的解决方案 . )
在进一步研究之前,请检查您控制的以下原因:
文件夹是否设置为进程的当前目录?如果是,请先将其更改为其他内容 .
您是否从该文件夹中打开了文件(或加载了DLL)? (并忘了关闭/卸载它)
否则,请检查您无法控制的以下合法原因:
该文件夹中有标记为只读的文件 .
您没有对其中某些文件的删除权限 .
文件或子文件夹在资源管理器或其他应用程序中打开 .
如果出现以上任何问题,您应该在尝试改进删除代码之前了解其原因 . 您的应用应该删除只读或无法访问的文件吗?是谁标记了他们,为什么?
一旦排除了上述原因,就有了仍有虚假失败的可能性 . 如果任何人拥有要删除的任何文件或文件夹的句柄,删除将失败,并且有很多原因可能导致某人枚举该文件夹或读取其文件:
搜索索引器
反病毒
备份软件
处理虚假失败的一般方法是多次尝试,在尝试之间暂停 . 你显然不想永远尝试,所以你应该在经过一定次数的尝试后放弃并抛出异常或忽略错误 . 像这样:
在我看来,这样的帮助应该用于所有删除,因为虚假的失败总是可能的 . 但是,您应该将此代码添加到您的使用案例中,而不是盲目地复制它 .
我的应用程序生成的内部数据文件夹的虚假失败,位于%LocalAppData%下,所以我的分析如下:
该文件夹仅由我的应用程序控制,并且用户没有正当理由将该文件夹中的内容标记为只读或无法访问,因此我不会尝试处理该情况 .
那里没有有 Value 的用户创建的东西,所以不存在强行删除错误的东西的风险 .
作为一个内部数据文件夹,我不希望它在资源管理器中打开,至少我觉得不需要专门处理这种情况(即我通过支持很好地处理这种情况) .
如果所有尝试都失败,我选择忽略该错误 . 最坏的情况是,该应用程序无法解压缩一些较新的资源,崩溃并提示用户联系支持,只要不经常发生,我就可以接受 . 或者,如果应用程序没有崩溃,它将留下一些旧数据,这也是我可以接受的 .
我选择将重试次数限制为500毫秒(50 * 10) . 这是一个在实践中起作用的任意门槛;我希望阈值足够短,以便用户不会杀死应用程序,认为它已停止响应 . 另一方面,半秒钟是罪犯完成处理我的文件夹的充足时间 . 从其他有时甚至可以接受的SO答案来判断,很少有用户会经历多次重试 .
我每50ms重试一次,这是另一个任意数字 . 我觉得如果在我尝试删除文件时正在处理(索引,检查)文件,那么50ms是在我的情况下期望完成处理的正确时间 . 此外,50ms足够小,不会导致明显的减速;再次,
Sleep(0)
在许多情况下似乎已经足够了,所以我们不想拖延太多 .代码重试任何IO异常 . 我通常不希望有任何异常访问%LocalAppData%,因此我选择了简单性并接受了500毫秒延迟的风险,以防发生合法异常 . 我也不想找到一种方法来检测我想要重试的确切异常 .
我在Delphi下遇到了同样的问题 . 最终的结果是我自己的应用程序锁定了我想要删除的目录 . 不知何故,当我写入目录(一些临时文件)时,目录被锁定 .
捕获22是,在删除它之前,我做了一个简单的 change directory 给它的父母 .
应该提到的一件重要事情(我将其添加为注释但我不允许)是重载的行为从.NET 3.5更改为.NET 4.0 .
从.NET 4.0开始,它删除文件夹本身但不在3.5中的文件 . 这也可以在MSDN文档中看到 .
.NET 4.0
.NET 3.5
我很惊讶没有人想到这个简单的非递归方法,它可以删除包含只读文件的目录,而无需更改每个文件的只读属性 .
(稍微更改一下,不要暂时触发cmd窗口,这可以通过互联网获得)
您可以通过运行以下内容重现错误:
当尝试删除目录'b'时,它会抛出IOException“目录不为空” . 这是愚蠢的,因为我们刚刚删除了目录'c' .
根据我的理解,解释是目录'c'被标记为已删除 . 但删除尚未在系统中提交 . 系统已完成作业的回复,而实际上它仍在处理中 . 系统可能正在等待文件资源管理器将焦点放在父目录上以提交删除 .
如果查看删除函数(http://referencesource.microsoft.com/#mscorlib/system/io/directory.cs)的源代码,您将看到它使用本机Win32Native.RemoveDirectory函数 . 这里记录了这种不等待的行为:
(http://msdn.microsoft.com/en-us/library/windows/desktop/aa365488(v=vs.85).aspx)
睡眠和重试是解决方案 . 如果ryascl的解决方案 .
现代异步答案
接受的答案是完全错误的,它可能适用于某些人,因为从磁盘获取文件所花费的时间可以释放锁定文件的任何内容 . 事实是,这是因为文件被某些其他进程/流/操作锁定 . 其他答案使用
Thread.Sleep
(Yuck)在一段时间后重试删除目录 . 这个问题需要重新审视一个更现代的答案 .单元测试
这些测试显示了锁定文件如何导致
Directory.Delete
失败以及上述TryDeleteDirectory
方法如何解决问题的示例 .我有一个奇怪的权限问题删除用户配置文件目录(在C:\ Documents and Settings中),尽管能够在Explorer shell中这样做 .
对我来说,“文件”操作对目录的作用是没有意义的,但我知道它有效,这对我来说已经足够了!
这个答案基于:https://stackoverflow.com/a/1703799/184528 . 与我的代码的不同之处在于,我们只会在必要时递归许多删除子目录和文件,第一次尝试时会失败调用Directory.Delete(这可能因为Windows资源管理器查看目录而发生) .
以上解决方案对我来说效果不佳 . 最后我使用了@ryascl解决方案的编辑版本,如下所示:
我花了几个小时来解决这个问题以及删除目录的其他异常 . 这是我的解决方案
此代码具有较小的延迟,这对我的应用程序来说并不重要 . 但要小心,如果您要删除的目录中有很多子目录,延迟可能会成为问题 .
不删除文件的递归目录删除肯定是意外的 . 我对此的解决方法:
我遇到过这种情况有帮助的情况,但一般来说,Directory.Delete会在递归删除时删除目录中的文件,如documented in msdn .
我不时会遇到这种不正常的行为,作为Windows资源管理器的用户:有时我无法删除文件夹(它认为荒谬的消息是“访问被拒绝”)但是当我向下钻取并删除较低的项目时,我可以删除上层项目也是如此 . 所以我猜上面的代码处理操作系统异常 - 而不是基类库问题 .
是否有可能存在竞争条件,其中另一个线程或进程正在向目录添加文件:
顺序是:
删除过程A:
清空目录
删除(现在为空)目录 .
如果其他人在1和2之间添加了一个文件,那么可能2会抛出列出的异常?
目录或其中的文件已锁定,无法删除 . 找到锁定它的罪魁祸首,看看你是否可以消除它 .
看来,在Windows资源管理器中选择路径或子文件夹足以阻止Directory.Delete(path,true)的单次执行,抛出如上所述的IOException并死亡,而不是将Windows资源管理器引导到父文件夹并作为预期 .
我今天遇到了这个问题 . 之所以发生这种情况,是因为我将Windows资源管理器打开到试图删除的目录,导致递归调用失败,从而导致IOException . 确保没有对目录打开的句柄 .
此外,MSDN很清楚,你不必写自己的错觉:http://msdn.microsoft.com/en-us/library/fxeahc5f.aspx
我在使用TFS2012的构建服务器上使用Windows Workflow Foundation时遇到了同样的问题 . 在内部,名为Directory.Delete()的工作流将递归标志设置为true . 在我们的案例中,它似乎与网络相关 .
我们正在删除网络共享上的二进制drop文件夹,然后重新创建并使用最新的二进制文件重新填充它 . 其他每个构建都会失败 . 在构建失败后打开drop文件夹时,该文件夹为空,这表示Directory.Delete()调用的每个方面都成功,除了删除实际目录 .
该问题似乎是由网络文件通信的异步性质引起的 . 构建服务器告诉文件服务器删除所有文件和文件服务器报告它有,即使它没有完全完成 . 然后构建服务器请求删除目录,文件服务器拒绝该请求,因为它尚未完全删除文件 .
在我们的案例中有两种可能的解
在我们自己的代码中构建递归删除,并在每个步骤之间进行延迟和验证
在IOException之后重试最多X次,在再次尝试之前给出延迟
后一种方法快速而肮脏,但似乎可以解决问题 .
这是因为FileChangesNotifications.
它发生在ASP.NET 2.0之后 . 当您删除应用程序中的某个文件夹时,它会 gets restarted . 您可以使用ASP.NET Health Monitoring自己查看 .
只需将此代码添加到web.config / configuration / system.web:
之后退房
Windows Log -> Application
. 到底是怎么回事:删除文件夹时,如果有任何子文件夹,
Delete(path, true)
首先删除子文件夹 . FileChangesMonitor知道它就足够了删除并关闭您的应用程序 . 同时您的主目录尚未删除 . 这是来自Log的事件:Delete()
没有完成它的工作,因为应用程序正在关闭,它引发了一个异常:当您在要删除的文件夹中 do not have any subfolders 时,删除()只会删除所有文件和该文件夹,应用程序也会重新启动,但是 don't get any exceptions ,因为重新启动后应用程序重新启动不会对请求做出响应,等等 .
What now?
有一些解决方法和调整来禁用此行为,Directory Junction,Turning Off FCN with Registry,Stopping FileChangesMonitor using Reflection (since there is no exposed method),但它们似乎都不对,因为FCN是有原因的 . 它正在管理您的应用程序的结构,这不是您的数据结构 . 简短的回答是:在应用程序之外放置要删除的文件夹 . FileChangesMonitor将不会收到任何通知,并且您的应用每次都不会重新启动 . 你不会有任何例外 . 要从网上看到它们,有两种方法:
创建一个处理传入呼叫的控制器,然后通过从应用程序外部的文件夹(wwwroot外部)读取文件 .
如果您的项目很大且性能最重要,请设置单独的小型和快速Web服务器来提供静态内容 . 因此,您将把他的特定工作留给IIS . 它可以在同一台机器上(用于Windows的mongoose)或另一台机器(用于Linux的nginx) . 好消息是你不需要支付额外的微软许可证来在linux上设置静态内容服务器 .
希望这可以帮助 .
如上所述,“接受”的解决方案在重新分析点上失败了 - 但是人们仍然将它标记出来(???) . 有一个更短的解决方案,可以正确复制功能:
我知道,不处理后面提到的权限情况,但是对于所有意图和目的,FAR BETTER提供原始/库存Directory.Delete()的 expected functionality - 并且代码也少得多 .
你可以安全地继续进行处理,因为旧的目录将会被取消...即使没有消失,因为'file system is still catching up'(或者MS提供破坏功能的任何借口) .
作为一个好处,如果你知道你的目标目录很大/很深并且不想等待(或者有异常烦恼),那么最后一行可以替换为:
你仍然可以安全地继续工作 .
当路径长度大于260个符号的目录(或任何子目录)中存在文件时,此问题可能出现在Windows上 .
在这种情况下,您需要删除
\\\\?\C:\mydir
而不是C:\mydir
. 关于260个符号限制,您可以阅读here .如果您的应用程序(或任何其他应用程序)的当前目录是您尝试删除的目录,则它不会是访问冲突错误,但目录不为空 . 通过更改当前目录确保它不是您自己的应用程序;此外,请确保该目录未在某些其他程序中打开(例如Word,Excel,Total Commander等) . 大多数程序将cd到最后打开的文件的目录,这将导致 .
当方法异步并且编码如下时,我解决了所述问题的一个可能的实例:
有了这个:
结论?有一些异步方面摆脱用于检查微软无法说话的存在的句柄 . 就好像if语句中的异步方法的if语句就像using语句一样 .
我用这种千年技术解决了(你可以把自己留在Thread.Sleep中)
上述答案都不适合我 . 看来我自己的应用程序在目标目录上使用
DirectoryInfo
导致它保持锁定状态 .强制垃圾收集似乎解决了这个问题,但不是马上解决 . 几次尝试删除所需的地方 .
请注意
Directory.Exists
,因为它可以在异常后消失 . 我不知道为什么删除对我的延迟(Windows 7 SP1)在第二个参数中添加true .
它将删除所有 .