我使用。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.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的溶液。

其他回答

我在使用TFS2012的构建服务器上使用Windows Workflow Foundation时遇到过同样的问题。在内部,工作流调用Directory.Delete()并将递归标志设置为true。在我们的案例中,这似乎与网络有关。

在用最新的二进制文件重新创建和重新填充网络共享上的二进制文件之前,我们正在删除它。其他的构建都会失败。在构建失败后打开删除文件夹时,文件夹为空,这表明directory. delete()调用的每个方面都是成功的,除了删除实际的目录。

这个问题似乎是由网络文件通信的异步特性引起的。构建服务器告诉文件服务器删除所有文件,文件服务器报告它已经删除了,即使它还没有完全完成。然后构建服务器请求删除目录,而文件服务器拒绝了该请求,因为它还没有完全完成文件的删除。

在我们的案例中有两个可能的解决方案:

在我们自己的代码中建立递归删除,每个步骤之间都有延迟和验证 在IOException发生后重试最多X次,在再次尝试之前给予延迟

后一种方法又快又脏,但似乎很管用。

If you are trying to recursively delete directory a and directory a\b is open in Explorer, b will be deleted but you will get the error 'directory is not empty' for a even though it is empty when you go and look. The current directory of any application (including Explorer) retains a handle to the directory. When you call Directory.Delete(true), it deletes from bottom up: b, then a. If b is open in Explorer, Explorer will detect the deletion of b, change directory upwards cd .. and clean up open handles. Since the file system operates asynchronously, the Directory.Delete operation fails due to conflicts with Explorer.

不完整的解决方案

我最初发布了以下解决方案,想法是中断当前线程,让资源管理器有时间释放目录句柄。

// 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。尚不清楚第一次删除尝试是否为第二次成功的删除尝试铺平了道路,或者仅仅是抛出/捕获异常导致的时间延迟,允许文件系统赶上。

通过在try块的开头添加Thread.Sleep(0),可以减少在典型条件下抛出和捕获的异常数量。此外,在系统负载较重的情况下,您可能会同时浏览这两个目录。删除尝试并失败。可以将此解决方案作为更健壮的递归删除的起点。

一般的答案

此解决方案仅处理与Windows资源管理器交互的特性。如果你想要一个坚如磐石的删除操作,有一件事要记住,任何东西(病毒扫描程序,无论什么)都可能在任何时候对你试图删除的东西有一个开放的句柄。所以你得稍后再试。多久之后,以及尝试了多少次,取决于删除对象的重要性。正如MSDN所示,

健壮的文件迭代代码必须考虑到许多复杂性 文件系统的。

这个无辜的声明,只提供了一个到NTFS参考文档的链接,应该会让你汗毛直竖。

(编辑:很多。这个答案原来只有第一个不完全解。)

有一件重要的事情需要提一下(我把它作为注释添加了进去,但是不允许我这样做),那就是重载的行为从。net 3.5改变到了。net 4.0。

Directory.Delete(myPath, true);

从。net 4.0开始,它会删除文件夹中的文件,但在3.5中不会。在MSDN文档中也可以看到这一点。

net 4.0

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

net 3.5

删除一个空目录,如果指定,删除目录中的任何子目录和文件。

现代异步回答

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

单元测试

这些测试展示了一个被锁定的文件如何导致目录的示例。删除失败以及上面的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);
        }
    }
}

你不必创建一个额外的递归方法或删除额外文件夹内的文件。这些都是通过调用自动完成的

DirectoryInfo.Delete ();

详情在这里。

像这样的东西效果很好:

  var directoryInfo = new DirectoryInfo("My directory path");
    // Delete all files from app data directory.

    foreach (var subDirectory in directoryInfo.GetDirectories())
    {
          subDirectory.Delete(true);// true set recursive paramter, when it is true delete sub file and sub folder with files too
    }

将true作为变量传递给delete方法,将删除包含文件的子文件和子文件夹。