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 而不将数据发送到另一个文件