System.IO.File.Delete() / System.IO.File.Move() 有时不起作用

Posted

技术标签:

【中文标题】System.IO.File.Delete() / System.IO.File.Move() 有时不起作用【英文标题】:System.IO.File.Delete() / System.IO.File.Move() sometimes does not work 【发布时间】:2011-06-24 11:13:32 【问题描述】:

Winforms 程序需要将一些运行时信息保存到 XML 文件中。该文件有时可能有几百千字节的大小。在 beta 测试期间,我们发现一些用户会毫不犹豫地看似随意地终止进程,并且偶尔会导致文件写入一半并因此损坏。

因此,我们将算法更改为保存到临时文件,然后删除真实文件并进行移动。

我们的代码目前看起来像这样..

private void Save()

    XmlTextWriter streamWriter = null;
    try
    
        streamWriter = new XmlTextWriter(xmlTempFilePath, System.Text.Encoding.UTF8);

        XmlSerializer xmlSerializer = new XmlSerializer(typeof(MyCollection));

        xmlSerializer.Serialize(streamWriter, myCollection);

        if (streamWriter != null)
            streamWriter.Close();

        // Delete the original file
        System.IO.File.Delete(xmlFilePath);

        // Do a move over the top of the original file 
        System.IO.File.Move(xmlTempFilePath, xmlFilePath);
    
    catch (System.Exception ex)
    
        throw new InvalidOperationException("Could not save the xml file.", ex);
    
    finally
    
        if (streamWriter != null)
            streamWriter.Close();
    

这几乎在实验室和生产中都有效。该程序在 12 台计算机上运行,​​此代码平均每 5 分钟调用一次。我们每天大约会遇到一两次异常:

System.InvalidOperationException: 
Could not save the xml file. 
---> System.IO.IOException: Cannot create a file when that file already exists.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.__Error.WinIOError()
at System.IO.File.Move(String sourceFileName, String destFileName)
at MyApp.MyNamespace.InternalSave()

就好像在发出 Move 之前实际上并未向硬盘发出 Delete 一样。

这发生在 Win7 机器上。

几个问题:有没有Flush() 的概念可以用于整个磁盘操作系统?这是我的代码、.net、操作系统或其他东西的错误吗?我应该输入一些Thread.Sleep(x) 吗?也许我应该做一个File.Copy(src, dest, true)?我应该写下面的代码吗? (但它看起来很傻。)

while (System.IO.File.Exists(xmlFilePath))

    System.IO.File.Delete(xmlFilePath);


// Do a move over the top of the main file 
bool done = false;
while (!done)

    try
    
        System.IO.File.Move(xmlTempFilePath, xmlFilePath);
        done = true;
    
    catch (System.IO.IOException)
    
        // let it loop
    

有人见过吗?

【问题讨论】:

顺便说一句,您应该捕获特定的异常而不是基本的Exception 您是否对这些文件夹运行病毒扫描程序?谁在何时阅读这些文件?相关:***.com/q/6350224/60761 @ChrisF:这是捕获Exception 非常好的情况之一。他正在包装异常,而不是吞下它们。 这些计算机上运行着病毒扫描程序。他们肯定会瞄准这个文件夹。无论如何,删除就是删除。代码仍然需要可靠地工作。我不能建议人们禁用他们的病毒扫描程序。 【参考方案1】:

您永远不能假设您可以在多用户多任务操作系统上删除文件并删除它。与另一个应用程序或对文件感兴趣的用户本身相比,您还可以运行对文件感兴趣的服务。病毒扫描程序和搜索索引器是典型的麻烦制造者。

此类程序会打开文件并尝试通过指定删除共享访问权限来最大程度地减少影响。这在 .NET 中也可用,它是 FileShare.Delete 选项。使用该选项,Windows 允许进程删除文件,即使文件已打开。它在内部被标记为“删除待处理”。该文件实际上并没有从文件系统中删除,在 File.Delete 调用之后它仍然存在。之后尝试打开文件的任何人都会收到拒绝访问错误。在文件对象的最后一个句柄关闭之前,文件实际上并没有被删除。

您可能会看到它的标题,这解释了为什么 File.Delete 成功但 File.Move 失败。您需要做的是首先 File.Move 文件,使其具有不同的名称。 然后重命名新文件,然后删除原文件。您要做的第一件事是删除一个可能带有重命名名称的杂散副本,它可能是由于电源故障而遗留下来的。

总结:

    创建文件.new 删除文件.tmp 将 file.xml 重命名为 file.tmp 将 file.new 重命名为 file.xml 删除文件.tmp

第 5 步失败并不重要。

【讨论】:

好答案。对 FileShare.Delete 的行为的断言是否有任何参考?【参考方案2】:

如何使用 Move.Copy 并将 overwrite 设置为 true 以便它覆盖您的应用程序状态,然后您可以删除您的临时状态?

您也可以附加到App_Exit 事件并尝试执行干净关闭?

【讨论】:

那如何回答这个问题。 使用覆盖复制可能在删除然后移动不考虑错误是“文件已存在”的情况下起作用。值得一试。【参考方案3】:

如果此应用程序中的多个线程可以调用 Save,或者如果程序的多个实例试图更新同一个文件(例如在网络共享上),您可能会得到一个竞争条件,使得该文件不会当两个线程/进程都尝试删除时存在,然后一个成功执行其Move(或Move 正在进行中),当第二个尝试使用相同的文件名并且第二个Move 将失败。

正如 anvarbek raupov 所说,您可以使用 File.Copy(String, String, Boolean) 允许发生覆盖(因此不再需要删除),但这意味着最后一个更新者获胜 - 您需要考虑这是否是您想要的(尤其是在多线程场景,如果你不走运,最后一个更新程序可能一直在使用旧状态)。

更安全的方法是强制每个线程/进程使用单独的文件,或实施某种形式的基于文件系统的锁定(例如,创建另一个文件来宣布“我正在处理这个文件”,更新主文件,然后删除这个锁文件)。

【讨论】:

我在帖子中没有提到它,但是代码已经通过系统范围的互斥锁得到了非常仔细的保护。并发不是问题。【参考方案4】:

正如汉斯所说,一种解决方法是先移动文件然后删除它。

也就是说,对于事务性 NTFS 的删除,我无法重现所描述的错误。查看 github.com/haf/Castle.Transactions 和相应的 nuget 包... 2.5 已针对文件事务进行了充分测试和记录。

在使用非事务性文件系统进行测试时,该项目单元测试的单元测试代码总是在删除之前移动。

3.0 目前处于 pre-alpha 状态,但会将常规事务与文件 io 与事务文件集成到更高的水平。

【讨论】:

以上是关于System.IO.File.Delete() / System.IO.File.Move() 有时不起作用的主要内容,如果未能解决你的问题,请参考以下文章

.net写本地文件的一个方法

C# 创建一个文件,以当前系统时间为文件名

文件处理

如何在 C# 中检查 File.Delete() 是不是会在不尝试的情况下成功?

度量快速开发平台中使用.NET,API函数

C# 在一行中继续 ForEach