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

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

谁看过这个吗?

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

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

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

此类程序打开文件并尝试通过指定删除共享访问来最小化影响。 这在.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

步骤5的失败并不重要。

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

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

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

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

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

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

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