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

18

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

文件的写入方式并没有什么特别之处,只是简单地创建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应该关闭文件句柄,并将任何未写入的内容刷新到磁盘:

在我的C#代码中,计算机是否等待输出完成后再继续?

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

System.IO.File.WriteAllText完成后,将把所有文本刷新到文件系统缓存,然后懒惰地写入驱动器。

因此,我认为这里发生的情况是文件被清除并用0x00初始化,但当系统崩溃时数据尚未写入。

我想可能会使用某种临时文件处理方式,流程如下:

  1. 将新内容写入临时文件中
  2. 删除原始文件
  3. 将临时文件重命名为原始文件

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

是否有任何方法可以强制机器将数据转储到磁盘而不是决定何时这样做,或者是否有更好的更新文件的方法?

更新:

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

  1. 使用FileOptions.WriteThrough标记创建一个带有新内容的临时文件
  2. 将数据写入磁盘(直到写入完成才会返回)
  3. 使用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);
}

1
我真诚地认为,在这种情况下,你花时间去确定导致系统崩溃的原因会更有价值。 - user2366842
2
它们只是人们机器上的随机崩溃,与我的系统无关(其中一个被报告为驱动程序问题,我想)。无论如何,作为软件开发人员,我的用户要求我确保我的程序在他们遇到蓝屏或其他情况时不会丢失其配置。 - antfx
1
你试过使用File.Create方法代替File.WriteAllText吗?它有一个有用的重载 - http://msdn.microsoft.com/en-us/library/ms143360(v=vs.110).aspx,它接受FileOptions参数,可以设置为`WriteThrough` - "表示系统应该直接写入磁盘,而不经过任何中间缓存。" - Ilya Luzyanin
1
你可以手动创建 FileStream 并使用 FileOptions.WriteThrough 选项。这个问题展示了如何禁用所有缓存,这只能通过 PInvoke 实现。 - Mike Zboray
1
@miked 如果你使用WriteThrough写入1GB,那么人们可能会直觉地认为可以部分写入数据。当调用返回后,数据会变得稳定,但在此之前,旧数据和新数据可能混合存在。 - usr
显示剩余6条评论
1个回答

15
您可以使用 FileStream.Flush 强制将数据写入磁盘。将数据写入临时文件中,然后使用 File.Replace 原子替换目标文件。
我相信这是能够保证成功的。文件系统提供了弱保证,这些保证很少被记录下来,而且非常复杂。
或者,如果可用的话,您可以使用事务性NTFS,它可以在 .NET 中使用。 FileOptions.WriteThrough 可以替代 Flush,但如果您的数据可能超过单个簇的大小,则仍需要临时文件。

那只影响了临时文件。数据就这样丢失了。旧数据保留不变。 - usr
有道理。我想在这种情况下,丢失新数据可能比损坏配置更好。 - user2366842
是的,保留旧数据的副本没问题。用户会失去一些信息,但并不多。 - antfx
@miked,数据丢失是一个无法解决的问题,因为机器可能会在你开始写入之前蓝屏。但是,崩溃一致性是可以解决的。 - usr
我已经使用新的写入函数更新了我的问题。我认为逻辑是正确的? - antfx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接