我使用。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?


当前回答

在Windows资源管理器中选择路径或子文件夹似乎足以阻止目录的一次执行。Delete(path, true),抛出如上所述的IOException和死亡,而不是引导Windows资源管理器到父文件夹并按预期进行。

其他回答

现代异步回答

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

目录或文件处于锁定状态,不能被删除。找到锁它的罪魁祸首,看看你是否能消除它。

如上所述,“可接受的”解决方案在重解析点上失败。 有一个更短的解决方案可以正确地复制功能:

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

我知道,不能处理后面提到的权限情况,但是FAR BETTER提供了原始/stock Directory.Delete()的预期功能,而且代码也少得多。

您可以安全地进行处理,因为旧的dir将被清除……即使没有消失,因为“文件系统仍在追赶”(或任何借口,微软提供了一个坏的功能)。

作为一个好处,如果你知道你的目标目录是大/深的,并且不想等待(或麻烦的异常),最后一行可以替换为:

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

你仍然可以安全地继续工作。

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

Directory.Delete(myPath, true);

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

net 4.0

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

net 3.5

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

这是因为FileChangesNotifications。

它发生在ASP。NET 2.0。当你删除应用程序中的某个文件夹时,它会重新启动。你可以自己看,用 ASP。NET运行状况监视。

只需将这段代码添加到你的web.config/configuration/system.web:

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

之后,检查Windows Log ->应用程序。 发生了什么:

删除文件夹时,如果有子文件夹,delete (path, true)会先删除子文件夹。这足以让FileChangesMonitor知道删除和关闭你的应用程序。同时你的主目录还没有删除。这是来自日志的事件:

Delete()没有完成它的工作,因为应用程序正在关闭,它引发了一个异常:

当你正在删除的文件夹中没有任何子文件夹时,Delete()只是删除所有文件和该文件夹,app也会重新启动,但你不会得到任何异常,因为app重新启动不会中断任何事情。但是,你仍然会失去所有进程中的会话,应用程序在重新启动时不响应请求,等等。

现在该做什么?

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:

创建一个控制器来处理传入调用,然后通过从应用程序外部(wwwroot外部)的文件夹读取文件返回。 如果你的项目很大,性能是最重要的,那就单独设置一个小型快速的web服务器来提供静态内容。因此,您将把他的具体工作留给IIS。它可以在同一台机器上(Windows的mongoose),也可以在另一台机器上(Linux的nginx)。好消息是你不需要支付额外的微软许可证来在linux上设置静态内容服务器。

希望这能有所帮助。