File.WriteAllText 不将数据刷新到磁盘

Posted

技术标签:

【中文标题】File.WriteAllText 不将数据刷新到磁盘【英文标题】:File.WriteAllText not flushing data to disk 【发布时间】:2014-10-11 13:22:11 【问题描述】:

我现在收到 3 份报告称用户的机器在使用我的软件时崩溃了。这些崩溃与我的程序无关,但是当他们重新启动配置文件时,我的程序写入的配置文件都已损坏。

文件的写入方式没有什么特别之处,只需创建一个 Json 表示并使用 File.WriteAllText() 将其转储到磁盘

// save our contents to the disk
string json = JsonConvert.SerializeObject(objectInfo, Formatting.Indented);

// write the contents
File.WriteAllText(path, json);

我有一个用户给我发了一个文件,长度看起来差不多(~3kb),但内容都是 0x00。

根据下面的帖子 File.WriteAllText 应该关闭文件句柄,将所有未写入的内容刷新到磁盘:

In my C# code does the computer wait until output is complete before moving on?

但是,正如 Alberto 在 cmets 中指出的那样:

System.IO.File.WriteAllText 完成后,会将所有文本刷新到 文件系统缓存,然后,它将被延迟写入驱动器。

所以我推测这里发生的事情是文件正在被清除并使用 0x00 初始化,但系统崩溃时尚未写入数据。

我在考虑可能使用某种临时文件,所以过程会是这样的:

    将新内容写入临时文件 删除原始文件 将临时文件重命名为原始文件

我认为这不会解决问题,因为我认为 Windows 只会移动文件,即使 IO 仍处于挂起状态。

有什么办法可以强制机器将该数据转储到磁盘,而不是由它决定何时执行此操作,或者可能是更新文件的更好方法?

更新:

根据@usr、@mikez 和@llya luzyanin 的建议,我创建了一个新的WriteAllText 函数,它使用以下逻辑执行写入:

    使用 FileOptions.WriteThrough 标志创建一个包含新内容的临时文件 将数据写入磁盘(在写入完成之前不会返回) File.Replace 将新临时文件的内容复制到真实文件,进行备份

按照这种逻辑,如果最终文件无法加载,我的代码会检查备份文件并改为加载该文件

代码如下:

public static void WriteAllTextWithBackup(string path, string contents)

    // generate a temp filename
    var tempPath = Path.GetTempFileName();

    // create the backup name
    var backup = path + ".backup";

    // delete any existing backups
    if (File.Exists(backup))
        File.Delete(backup);

    // get the bytes
    var data = Encoding.UTF8.GetBytes(contents);

    // write the data to a temp file
    using (var tempFile = File.Create(tempPath, 4096, FileOptions.WriteThrough))
        tempFile.Write(data, 0, data.Length);

    // replace the contents
    File.Replace(tempPath, path, backup);

【问题讨论】:

老实说,我认为您最好花时间在这种情况下,确定导致系统崩溃的原因。 它们只是人们机器上的随机崩溃,与我的系统无关(我认为其中一个被报告为驱动程序问题)。无论如何,重点是,作为软件开发人员,我的用户要求我确保我的程序在收到 BSOD 或其他情况时不会丢失其配置 您是否尝试过使用File.Create 方法而不是File.WriteAllText?它有有用的重载——msdn.microsoft.com/en-us/library/ms143360(v=vs.110).aspx,它带有 FileOptions 参数,可以设置为WriteThrough——“指示系统应该通过任何中间缓存写入并直接进入磁盘。” 您可能会考虑手动创建FileStream 并使用FileOptions.WriteThrough 选项。 This 问题展示了如何禁用所有缓冲,这只能通过 PInvoke 完成。 @miked 如果您使用 WriteThrough 写入 1GB 我认为相信可以部分写入数据是直观的。当调用返回时数据是稳定的,但在此之前您可以混合新旧数据。 【参考方案1】:

您可以使用FileStream.Flush 将数据强制写入磁盘。写入临时文件并使用File.Replace 原子替换目标文件。

我相信这肯定会奏效。文件系统提供的保证很弱。这些保证几乎没有记录在案,而且很复杂。

或者,如果可用,您可以使用事务性 NTFS。它适用于 .NET。

FileOptions.WriteThrough 可以替换 Flush,但如果您的数据可以超过单个集群的大小,您仍然需要临时文件。

【讨论】:

这只影响临时文件。那么数据就直接丢失了。旧数据保留。 有道理。我想在这种情况下,丢失新数据可能比损坏的配置更好。 是的,有一份旧数据的副本很好.. 用户丢失了一些信息,但不多 @miked 丢失数据是一个无法解决的问题,因为机器可能会在您开始写作之前蓝屏。不过,崩溃一致性是可以解决的。 我已经用新的 write 函数更新了我的问题。我认为逻辑是合理的?

以上是关于File.WriteAllText 不将数据刷新到磁盘的主要内容,如果未能解决你的问题,请参考以下文章

C# 中的 File.WriteAllText() 语句不创建文件

首先在 StringBuilder 中存储的 File.AppendText 或 File.WriteAllText 消耗的资源更少,速度更快?

从后台往磁盘上写入内容

使用 AJAX 和 jQuery 验证 From 而不将数据发送到另一个文件

为啥不将 JWT 访问令牌存储在内存中并在 cookie 中刷新令牌?

即使不将其添加为子视图,设置为 TableViewController 的刷新控件也可以工作