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

时间:2011-06-24 11:13:33

标签: c# move ioexception delete-file

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

有没有人见过这个?

4 个答案:

答案 0 :(得分:13)

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

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

你可能会看到它的标题,这解释了File.Delete成功但File.Move失败的原因。你需要做的是File.Move文件,所以它有一个不同的名称。 然后重命名新文件,然后删除原始文件。你做的第一件事就是删除一个带有重命名的可能的杂散副本,它可能因电源故障而被遗忘。

汇总:

  1. 创建file.new
  2. 删除file.tmp
  3. 将file.xml重命名为file.tmp
  4. 将file.new重命名为file.xml
  5. 删除file.tmp
  6. 步骤5的失败并不重要。

答案 1 :(得分:3)

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

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

答案 2 :(得分:1)

如果此应用程序中的多个线程可以调用Save,或者该程序的多个实例正在尝试更新同一文件(例如在网络共享上),则可能会出现争用条件,导致文件无效当两个线程/进程都尝试删除时存在,然后一个成功执行其Move(或Move正在进行中),当第二个尝试使用相同的文件名时,第二个{{ 1}}会失败。

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

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

答案 3 :(得分:0)

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

那说使用Transactional NTFS'删除,我无法重现所描述的错误。查看github.com/haf/Castle.Transactions和相应的nuget软件包... 2.5已经过完善的测试并记录了文件交易。

使用非事务处理文件系统进行测试时,此项目单元测试的单元测试代码在删除之前始终会移动。

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