不能用Directory.Delete删除目录(path,true)

我正在使用.NET 3.5,尝试recursion删除一个目录使用:

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

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

有没有一个原因,我会看到这个,而不是AccessViolationException?

编者按:虽然这个答案包含了一些有用的信息,但实际上对于Directory.Delete的工作是不正确的。 请阅读这个答案的评论,以及这个问题的其他答案。


我之前遇到过这个问题。

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

把这个代码放到你的项目中。

 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:\上调用此函数。

如果你试图recursion地删除目录a和目录a\b在资源pipe理器中打开, b将被删除,但是你会得到'目录不是空的'错误,即使它是空的,当你去看看。 任何应用程序(包括资源pipe理器)的当前目录都会保留目录的句柄 。 当你调用Directory.Delete(true) ,它从下往上删除: b ,然后a 。 如果b在资源pipe理器中打开,资源pipe理器将检测到b的删除,向上改变目录cd ..并清理打开的句柄。 由于文件系统asynchronous操作, Directory.Delete操作由于与资源pipe理器冲突而失败。

不完整的解决scheme

我最初发布了下面的解决scheme,以中断当前线程的想法,以允许资源pipe理器时间释放目录句柄。

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

但是,这只有在打开的目录是你正在删除的目录的直接子目录时才有效。 如果在资源pipe理器中打开a\b\c\d ,并且您在a上使用a这种方法,则删除dc后,此方法将失败。

一个更好的解决scheme

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

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

尽pipe我们自己recursion的额外工作,但是我们仍然需要处理可能发生的UnauthorizedAccessException 。 目前尚不清楚第一次删除尝试是否为第二个成功的尝试铺平了道路,或者仅仅是由于引发/捕获exception而导致的时间延迟,从而使文件系统赶上。

您可以通过在try块的开始处添加Thread.Sleep(0)来减less在典型情况下抛出和捕获的exception数量。 此外,在系统负载过重的情况下,您可能会通过两个Directory.Delete尝试并且失败。 考虑这个解决scheme是一个更强大的recursion删除的起点。

一般的答案

此解决scheme只解决与Windows资源pipe理器交互的特性。 如果你想要一个坚如磐石的删除操作,有一点要记住的是,任何东西(病毒扫描,无论什么)都可以随时打开你要删除的东西。 所以你必须稍后再试。 之后多less次以及多less次尝试取决于删除对象的重要性。 正如MSDN所示 ,

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

这个纯粹的声明,只有链接到NTFS参考文件,应该让你的头发站起来。

编辑 :很多,这个答案本来只有第一个不完整的解决scheme。)

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

  • 该文件夹是否被设置为您的stream程的当前目录? 如果是的话,先把它改成别的东西。
  • 你是否从该文件夹打开一个文件(或加载了一个DLL)? (并忘记closures/卸载它)

否则,请检查您的控制之外的以下合法理由:

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

如果上述任何一个问题,您应该明白为什么它发生之前,试图改善您的删除代码。 您的应用程序是否应删除只读或无法访问的文件? 谁是这样标记的,为什么?

一旦你排除了上述原因,虚假故障仍然存在。 如果任何人持有被删除的文件或文件夹的句柄,删除操作将失败,为什么有人可能正在枚举文件夹或读取文件:

  • search索引器
  • 反病毒软件
  • 备份软件

处理虚假故障的一般方法是多次尝试,暂停尝试。 你显然不想永远尝试,所以你应该放弃一定的尝试次数,并抛出exception或忽略错误。 喜欢这个:

 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%下,所以我的分析如下所示:

  1. 该文件夹完全由我的应用程序控制,并且用户没有正当的理由去标记事物在该文件夹内是只读或不可访问的,所以我不试图处理这种情况。

  2. 那里没有有价值的用户创build的东西,所以没有错误地强行删除某些东西的风险。

  3. 作为一个内部的数据文件夹,我不指望它是在资源pipe理器中打开,至less我不觉得有必要专门处理这种情况(即我很好地处理这种情况下通过支持)。

  4. 如果所有的尝试失败,我select忽略错误。 最糟糕的情况是,应用程序未能解压缩一些新的资源,崩溃并提示用户联系支持,只要不经常发生,我可以接受。 或者,如果应用程序不崩溃,它会留下一些旧数据,这是我可以接受的。

  5. 我select限制重试500ms(50 * 10)。 这是一个在实践中起作用的武断的门槛。 我想要的阈值是足够短,以便用户不会杀了应用程序,认为它已经停止响应。 另一方面,半秒钟是犯罪人完成我的文件夹处理的时间。 从其他有时甚至可以findSleep(0) SO答案来看,只有极less数的用户经历过一次以上的重试。

  6. 我每50ms重试一次,这是另一个任意数字。 我觉得,如果一个文件正在处理(索引,检查),当我试图删除它,50ms是在适当的时候期望处理完成在我的情况。 此外,50ms足够小,不会导致明显的放缓; 在很多情况下, Sleep(0)似乎已经足够,所以我们不想拖延太多。

  7. 代码重试任何IOexception。 我通常不会期望访问%LocalAppData%的任何exception,所以我select了简单性,并接受了在发生合法exception的情况下500ms延迟的风险。 我也不想找出一种方法来检测我想重试的确切exception。

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

catch 22是,我做了一个简单的改变目录到它的父母在删除之前。

我很惊讶没有人想到这个简单的非recursion方法,它可以删除包含只读文件的目录,而不需要改变它们每个的只读属性。

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

(稍微改变一下,不要立刻开启一个cmd窗口,这可以在互联网上find)

您可以通过运行以下代码来重现错误:

 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'被标记为删除。 但删除还没有在系统中提交。 系统已经回复了工作,而实际上,它仍在处理中。 系统可能会等待文件资源pipe理器重点在父目录提交删除。

如果您查看Delete函数( http://referencesource.microsoft.com/#mscorlib/system/io/directory.cs )的源代码,您将看到它使用本机Win32Native.RemoveDirectory函数。 这个不要等待的行为在这里被注意到:

RemoveDirectory函数在closures时标记要删除的目录。 因此,直到该目录的最后一个句柄closures,该目录才被删除。

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

睡眠和重试是解决scheme。 参考ryascl的解决scheme。

尽pipe能够在资源pipe理器shell中这样做,但我还是有一些奇怪的权限问题,即删除用户configuration文件目录(在C:\ Documents and Settings中)。

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

对于一个目录上的“文件”操作做什么是没有意义的,但是我知道它是有效的,这对我来说已经足够了!

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

 Directory.Delete(myPath, true); 

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

.NET 4.0

删除指定的目录 ,如果指示,则删除目录中的任何子目录和文件。

.NET 3.5

删除一个空目录 ,如果有指示,则删除目录中的所有子目录和文件。

这个答案是基于: https : //stackoverflow.com/a/1703799/184528 。 与我的代码的区别在于,我们只在必要时recursion许多删除子目录和文件。Directory.Delete在第一次尝试(由于Windows资源pipe理器正在查看目录时可能发生)失败。

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

现代asynchronous答案

被接受的答案显然是错误的,它可能对一些人有用,因为从磁盘获取文件所花费的时间可以释放locking文件的任何东西。 事实是,这是因为文件被其他进程/stream/操作locking了。 其他答案使用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; } 

unit testing

这些testing显示了locking文件如何导致Directory.Delete失败以及上面的TryDeleteDirectory方法如何解决问题的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); } } } 

我已经花了几个小时来解决这个问题和其他例外删除目录。 这是我的解决scheme

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

这段代码有很小的延迟,这对我的应用程序来说并不重要。 但要小心,如果你想删除的目录中有很多子目录,延迟可能是一个问题。

不删除文件的recursion目录删除肯定是意外的。 我的解决方法是:

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

我遇到过这种情况,但一般情况下,Directory.Delete会在recursion删除时删除目录中的文件,如msdn中所述 。

有时候我也会遇到这种不规则的行为,也是Windows资源pipe理器的用户:有时我不能删除一个文件夹(它认为这个无意义的消息是“拒绝访问”),但是当我钻取并删除较低的项目时,我可以删除上层项目也是如此。 所以我想上面的代码处理操作系统exception – 而不是基类库的问题。

是否有另一个线程或进程将文件添加到目录的竞争条件:

序列将是:

删除进程A:

  1. 清空目录
  2. 删除(现在是空的)目录。

如果其他人添加1和2之间的文件,那么也许2会抛出exception列出?

It appears that having the path or subfolder selected in Windows Explorer is enough to block a single execution of Directory.Delete(path, true), throwing an IOException as described above and dying instead of booting Windows Explorer out to a parent folder and proceding as expected.

I had this problem today. It was happening because I had windows explorer open to the directory that was trying to be deleted, causing the recursive call the fail and thus the IOException. Make sure there are no handles open to the directory.

Also, MSDN is clear that you don't have to write your own recusion: http://msdn.microsoft.com/en-us/library/fxeahc5f.aspx

I've had this same problem with Windows Workflow Foundation on a build server with TFS2012. Internally, the workflow called Directory.Delete() with the recursive flag set to true. It appears to be network related in our case.

We were deleting a binary drop folder on a network share before re-creating and re-populating it with the latest binaries. Every other build would fail. When opening the drop folder after a failed build, the folder was empty, which indicates that every aspect of the Directory.Delete() call was successful except for deleting the actually directory.

The problem appears to be caused by the asynchronous nature of network file communications. The build server told the file server to delete all of the files and the file server reported that it had, even though it wasn't completely finished. Then the build server requested that the directory be deleted and the file server rejected the request because it hadn't completely finished deleting the files.

Two possible solutions in our case:

  • Build up the recursive deletion in our own code with delays and verifications between each step
  • Retry up to X times after an IOException, giving a delay before trying again

The latter method is quick and dirty but seems to do the trick.

This is because of FileChangesNotifications.

It happens since ASP.NET 2.0. When you delete some folder within an app, it gets restarted . You can see it yourself, using ASP.NET Health Monitoring .

Just add this code to your web.config/configuration/system.web:

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

After that check out Windows Log -> Application . What is going on:

When you delete folder, if there is any sub-folder, Delete(path, true) deletes sub-folder first. It is enough for FileChangesMonitor to know about removal and shut down your app. Meanwhile your main directory is not deleted yet. This is the event from Log:

在这里输入图像说明

Delete() didn't finish its work and because app is shutting down, it raises an exception:

在这里输入图像说明

When you do not have any subfolders in a folder that you are deleting, Delete() just deletes all files and that folder, app is getting restarted too, but you don't get any exceptions , because app restart doesn't interrupt anything. But still, you lose all in-process sessions, app doesn't response to requests when restarting, etc.

What now?

There are some workarounds and tweaks to disable this behaviour, Directory Junction , Turning Off FCN with Registry , Stopping FileChangesMonitor using Reflection (since there is no exposed method) , but they all don't seem to be right, because FCN is there for a reason. It is looking after structure of your app , which is not structure of your data . Short answer is: place folders you want to delete outside of your app. FileChangesMonitor will get no notifications and your app will not be restarted every time. You will get no exceptions. To get them visible from the web there are two ways:

  1. Make a controller that handles incoming calls and then serves files back by reading from folder outside an app (outside wwwroot).

  2. If your project is big and performance is most important, set up separate small and fast webserver for serving static content. Thus you will leave to IIS his specific job. It could be on the same machine (mongoose for Windows) or another machine (nginx for Linux). Good news is you don't have to pay extra microsoft license to set up static content server on linux.

希望这可以帮助。

As mentioned above the "accepted" solution fails on reparse points – yet people still mark it up(???). There's a much shorter solution that properly replicates the functionality:

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

I know, doesn't handle the permissions cases mentioned later, but for all intents and purposes FAR BETTER provides the expected functionality of the original/stock Directory.Delete() – and with a lot less code too .

You can safely carry on processing because the old dir will be out of the way …even if not gone because the 'file system is still catching up' (or whatever excuse MS gave for providing a broken function) .

As a benefit, if you know your target directory is large/deep and don't want to wait (or bother with exceptions) the last line can be replaced with:

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

You are still safe to carry on working.

This problem can appear on Windows when there are files in a directory (or in any subdirectory) which path length is greater than 260 symbols.

In such cases you need to delete \\\\?\C:\mydir instead of C:\mydir . About the 260 symbols limit you can read here .

The directory or a file in it is locked and cannot be deleted. Find the culprit who locks it and see if you can eliminate it.

If your application's (or any other application's) current directory is the one you're trying to delete, it will not be an access violation error but a directory is not empty. Make sure it's not your own application by changing the current directory; also, make sure the directory is not open in some other program (eg Word, excel, Total Commander, etc.). Most programs will cd to the directory of the last file opened, which would cause that.

in case of network files, Directory.DeleteHelper(recursive:=true) might cause IOException which caused by the delay of deleting file

I think that there is a file open by some stream you are not aware of I had the same problem and solved it by closing all the streams that where pointing to the directory I wanted to delete.

This error occurs if any file or directory is considered in-use. It is a misleading error. Check to see if you have any explorer windows or command-line windows open to any directory in the tree, or a program that is using a file in that tree.

I resolved one possible instance of the stated problem when methods were async and coded like this:

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

结论? There is some asynchronous aspect of getting rid of the handle used to check existence that Microsoft has not been able to speak to. It's as if the asynchronous method inside an if statement has the if statement acting like a using statement.

None of the above answers worked for me. It appears that my own app's usage of DirectoryInfo on the target directory was causing it to remain locked.

Forcing garbage collection appeared to resolve the issue, but not right away. A few attempts to delete where required.

Note the Directory.Exists as it can disappear after an exception. I don't know why the delete for me was delayed (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."); 

add true in the second param.

 Directory.Delete(path, true); 

It will remove all.