【问题标题】:File.Exists returns true after File.DeleteFile.Exists 在 File.Delete 之后返回 true
【发布时间】:2018-05-11 15:08:16
【问题描述】:

我有以下方法来删除具有提供路径的文件

private void DestroyFile(string path)
{
    try
    {
        if (File.Exists(path))
        {
            File.Delete(path);
        }
        if (File.Exists(path))
        {
            throw new IOException(string.Format("Failed to delete file: '{0}'.", path));
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

如果文件在 File.Delete 方法之后存在,我将得到抛出的 IOException。具体

System.IO.IOException):无法删除文件:'C:\Windows\TEMP\[FILE NAME]'。

我也确认执行完成后路径变量中的位置不存在该文件。我想知道在 File.Delete 之后文件系统更新和使用 File.Exists 再次检查之间是否存在竞争条件。有没有更好的方法可以顺利删除?我知道如果文件不存在, File.Delete 不会返回错误,所以这些检查可能有点多余。我应该检查文件是否正在使用而不是它是否存在?

一些重要的附加信息: 该程序可以并且确实经常成功运行,但最近经常出现此特定错误。

【问题讨论】:

  • 此声明...I have also confirmed that the file does not exist at the location in the path variable after the execution is complete. ...与这种可能性相矛盾...I am wondering if I am running up against a race condition between the file system updating after File.Delete and checking against it again with File.Exists. 我强烈怀疑在您致电Delete() 后有什么东西正在创建文件。跨度>
  • 我认为C:\Windows 是一个锁定目录。应用程序必须是管理员才能删除。此外,由于它位于 Windows 目录中,因此可能有某些东西正在使用该文件。
  • @Greg 我查看了权限,我可以排除它。该方法在某些时候确实有效,这可能是一个重要的细节
  • @JᴀʏMᴇᴇ 澄清一下,我的意思是,在 File.Delete 之后缓存可能没有完全更新,因此当 File.Exists 检查通过时,文件仍然以某种容量存在。我正在使用 GetTempFileName() 方法来创建临时文件,尽管没有传递任何参数。我想知道这是否会导致重复使用文件名,从而导致您提出的问题。

标签: c# file-io


【解决方案1】:

File.Delete标记文件删除。只有当文件的所有句柄都关闭时,文件才会真正被删除(如果没有这样的句柄 - 它总是在 File.Delete 返回后被删除)。正如DeleteFile winapi 函数(由 C# File.Delete 使用)所记录的那样:

DeleteFile 函数在关闭时标记要删除的文件。所以, 在文件的最后一个句柄被删除之前,不会发生文件删除 关闭

您删除的文件通常没有打开的句柄。或者,如果有打开的句柄——它们通常没有“删除”共享(这个共享允许另一个进程将文件标记为删除),所以当你尝试删除这样的文件时——它要么被删除(没有打开的句柄)要么访问被拒绝或类似的异常被抛出(一些句柄,但没有删除共享)。

但是,有时某些软件(例如防病毒软件或搜索索引器)可能会通过“删除”共享打开任意文件并将它们保留一段时间。如果您尝试删除此类文件 - 它不会出错,并且当该软件关闭其句柄时文件确实会被删除。但是,File.Exists 将针对此类“待删除”文件返回 true。

您可以使用这个简单的程序重现此问题:

public class Program {
    public static void Main() {
        string path = @"G:\tmp\so\tmp.file";
        // create file with delete share and don't close handle
        var file = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.Delete);
        DestroyFile(path);
        GC.KeepAlive(file);
    }

    private static void DestroyFile(string path) {
        try {

            if (File.Exists(path)) {
                // no error
                File.Delete(path);
            }
            // but still exists
            if (File.Exists(path)) {
                throw new IOException(string.Format("Failed to delete file: '{0}'.", path));
            }
        }
        catch (Exception ex) {
            throw ex;
        }
    }
}

您可以在上面的程序中重试File.Exists 永远检查 - 文件将一直存在,直到您关闭句柄。

这就是您的情况 - 某些程序已使用 FileShare.Delete 打开此文件的句柄。

你应该预料到这种情况。例如 - 只需删除 File.Exists 检查,因为您将文件标记为删除,无论如何它都会被删除。

【讨论】:

  • 哇,这简直是疯了。感谢您的提醒。假设您在 async 方法中,使用此行删除后将等到文件实际删除后再继续:while (File.Exists(path)) await Task.Delay(100);
  • @Grinn 事情是很少有理由这样做。关闭最后一个句柄时文件将被删除,因此您可以继续前进。除非你想创建另一个文件而不是删除,但在这种情况下只是覆盖内容而不删除。
  • 我同意。在我的情况下,我有一个应用程序,它有多个线程,每个线程在处理它们时标记它们正在处理的文件路径,然后在取消标记路径之前删除它们。在极少数情况下,由于您概述的情况,文件在从驱动器中物理删除之前被取消标记。这导致了意外的行为,因为其他线程会获取有问题的文件。所以,谢谢!
【解决方案2】:

虽然它没有记录在 API 中,File.Delete 将在文件被完全删除之前返回。

这就是您遇到您遇到的情况的原因。删除调用会检查所有会导致删除失败的东西(现有句柄、锁、权限等),它会在Delete请求发起后返回

因此,在等待文件消失或使用FileSystemWatcher 来监视已删除事件之后,只需放置一个while 循环就相对安全了

【讨论】:

    【解决方案3】:

    File.Delete 和来自System.IO 的大多数方法通常都依赖于文件系统/流/等,它们有点过自己的生活,并且不是托管资源,因此File.Delete 可以在文件被物理删除之前返回,但是在它被标记为删除之后。

    File.Delete返回后,你可以确定文件会被删除,否则这个方法会自己抛出异常,所以第二次检查File.Exists并抛出IOException是不必要的。

    如果您想要自定义异常,请从File.Delete 捕获异常。

    在附加的代码中,请记住 throw ex;throw; 不同,并将堆栈跟踪更改为当前行。

    【讨论】:

      猜你喜欢
      • 2013-02-26
      • 2010-11-02
      • 1970-01-01
      • 2013-06-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-02
      • 1970-01-01
      相关资源
      最近更新 更多