当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《无法使用Directory.Delete删除目录(路径,true)》经验,为你挑选了9个好方法。

我正在使用.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?



1> Jeremy Edwar..:

编者注:尽管这个答案包含一些有用的信息,但事实上它的工作原理是不正确的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:\.


这是无稽之谈.Directory.Delete(myPath,true)是一个重载,它删除目录结构中的所有文件.如果你想出错,请回答Ryan S的错误.
如果您的删除目录中包含指向其他文件夹的快捷方式/符号链接,请注意此方法 - 您最终可能会删除超出预期的内容
+1,因为虽然Directory.Delete()确实删除了其子目录中的文件(使用recursive = true),但如果其中一个子目录或文件是只读的,则会抛出"IOException:Directory is not empty".所以这个解决方案比Directory.Delete()效果更好
-1有人可以说清楚这种方法的有效性是非常有疑问的.如果`Directory.Delete(string,bool)`失败,就会锁定或误解某些内容,并且没有一种适合这种问题的解决方案.人们需要在他们的背景下解决这个问题,我们不得不在这个问题(重试和吞咽异常)的每一个想法上大肆宣传,并希望获得良好的结果.
你的`Directory.Delete(path,true)`没有删除文件的说法是错误的.请参阅MSDN http://msdn.microsoft.com/en-us/library/fxeahc5f.aspx
真的吗?看起来像是一个简单的.NET扩展.为什么框架设计者会选择省略这个?
这个答案有助于解决只读异常问题.尽管Directory.Delete没有重载,但至少这个代码片段确实存在蹩脚!谢谢,杰里米爱德华兹!
对我不起作用.如果某些子文件夹在资源管理器中打开,仍会获得"目录不为空"异常.
上述四条评论均来自2010年,参考了"Ryan S."的答案.在过去的8年中,Ryan S.显然已经变成了@ryascl,又名Ryan Smith.
是的,@ Chanakya是对的.`Directory.Delete(path,true)`不删除符号链接指向的文件/文件夹.他建议的方法呢!为了避免这种调用`File.GetAttributes`并检查`FileAttributes.ReparsePoint`位.如果已设置,请确保在删除之前将其从文件/文件夹属性中删除.同样在文件夹的情况下,不要递归地在符号链接上调用自己的函数.而只需使用`Directory.Delete(dir,false)`删除它,它应该工作.

2> ryscl..:

如果你试图以递归方式删除目录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参考文档的链接,应该让你的头发站起来.

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


有人可以解释为什么解决方案有效吗?
它确实显示调用Directory.Delete(path,true)而路径或路径下的某个文件夹/文件在Windows资源管理器中打开或选择将抛出IOException.关闭Windows资源管理器并重新运行我现有的代码没有上面建议的try/catch工作正常.
发生的事情是系统要求Explorer"释放目录句柄",然后尝试删除目录.如果没有及时删除目录句柄,则会引发异常并执行`catch`块(同时,Explorer仍然会释放该目录,因为没有发送命令告诉它不要这样做).对"Thread.Sleep(0)"的调用可能需要也可能不需要,因为`catch`块已经为系统提供了更多的时间,但它确实为低成本提供了一点额外的安全性.之后,调用`Delete`,目录已经发布.

3> Andrey Taran..:

在进一步研究之前,请检查您控制的以下原因:

文件夹是否设置为进程的当前目录?如果是,请先将其更改为其他内容.

您是否从该文件夹中打开了文件(或加载了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毫秒延迟的风险,以防发生合法异常.我也不想找到一种方法来检测我想要重试的确切异常.


PPS几个月后,我很高兴地报告这段(有些疯狂的)代码完全解决了这个问题.有关此问题的支持请求降至零(每周约1-2次).
@RubenBartelink好的,所以我认为我们可以就此达成一致:发布一段适用于某个特定应用程序的代码(并且从未打算适用于所有情况)作为SO答案对许多新手来说都是一种伤害/或无知的开发者.我把它作为定制的起点,但是,有些人会按原样使用它,这是一件坏事.
@nopara你不需要比较; 如果我们离开了循环,我们就失败了.是的,在许多情况下,您将要抛出异常,然后在堆栈中添加适当的错误处理代码,可能是用户可见的消息.

4> 小智..:

应该提到的一件重要事情(我将其添加为注释,但我不允许)是重载的行为从.NET 3.5更改为.NET 4.0.

Directory.Delete(myPath, true);

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

.NET 4.0

删除指定的目录,如果指示,则删除目录中的所有子目录和文件.

.NET 3.5

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


我认为这只是一个文档更改...如果它只删除一个"空目录",那么还意味着删除目录中的文件,使用2°参数?如果它是空的,则没有文件......

5> Drejc..:

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

捕获22是,我在删除它之前为它的父进行了一个简单的更改目录.


+1现在有一些东西[Directory.Delete的msdn](http://msdn.microsoft.com/en-us/library/fxeahc5f.aspx)确实提到了!
有完整源代码示例的最终解决方案吗?

6> Muhammad Reh..:
现代异步答案

接受的答案是完全错误的,它可能适用于某些人,因为从磁盘获取文件所花费的时间可以释放锁定文件的任何内容.事实是,这是因为文件被某些其他进程/流/操作锁定.其他答案使用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);

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



7> Piyush Soni..:

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

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

(稍微更改一下,不要暂时触发cmd窗口,这可以通过互联网获得)


如果你开始依赖外部组件来处理框架中的内容,那么它就是一个"不太理想"的想法,因为它不再是可移植的(或者更难).如果exe不存在怎么办?或/选项改变了?如果Jeremy Edwards的解决方案有效,那么应该首选恕我直言

8> Olivier de R..:

您可以通过运行以下方式重现错误:

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的解决方案.



9> 小智..:

我有一个奇怪的权限问题删除用户配置文件目录(在C:\ Documents and Settings中),尽管能够在Explorer shell中这样做.

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

对我来说,"文件"操作对目录的作用没有任何意义,但我知道它有效,这对我来说已经足够了!


仍然没有希望,当目录有很多文件,资源管理器打开包含这些文件的文件夹.
推荐阅读
路人甲
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有