如何确保所有数据已经被物理写入磁盘?

23

我知道.NET FileStream的Flush方法只会将当前缓冲区写入磁盘,但这取决于Windows的磁盘驱动程序和硬盘固件,无法保证数据实际上已经物理写入磁盘。

是否存在一种.NET或Win32方法可以给我这个保证?所以,如果在调用此方法返回后的一纳秒内出现停电,我仍然可以确信一切都是正常的?

7个回答

17

Stefan S. 说:

我知道 .NET FileStream 的 Flush 方法只向磁盘写入当前缓冲区。

不,.NET FileStream 的 Flush 只是将 .NET 缓存写入操作系统缓存,它不会将操作系统缓存刷新到磁盘上。遗憾的是,该类的 MSDN 文档并没有说明这一点。对于 .NET < 4.0,您需要调用 Flush + Win32 的 FlushFilebuffers:

using System.Runtime.InteropServices;
. . .

// start of class:
[DllImport("kernel32", SetLastError=true)]
private static extern bool FlushFileBuffers(IntPtr handle);
. . .

stream.Flush();     // Flush .NET buffers to OS file cache.
#pragma warning disable 618,612 // disable stream.Handle deprecation warning.
if (!FlushFileBuffers(stream.Handle))   // Flush OS file cache to disk.
#pragma warning restore 618,612
{
  Int32 err = Marshal.GetLastWin32Error();
  throw new Win32Exception(err, "Win32 FlushFileBuffers returned error for " + stream.Name);
}

对于.NET 4.0,你可以使用新的flush(true)方法。2012年11月9日更新:微软的一个错误报告这里说它是有问题的,后来被修复了,但没有说修复在哪个版本或服务包中!听起来像是因为内部的.NET FileStream缓冲区为空时,Flush(true)不起作用?


9
在Windows下,可以查看FlushFileBuffers(Win32 API)(链接)。

谢谢 :) 我已经快速进行了性能测试,发现FileStream.Flush()太快了,不可思议。在FileStream的SafeFileHandle上使用FlushFileBuffers就像我预期的那样慢(在我的测试中比Flush()慢100倍)。 - Stefan Schultze
1
我发现调用FlushFileBuffers可能会导致异常(http://stackoverflow.com/q/9195807/4540)。在.NET 4下,像@jimvfr建议的那样,只需调用FileStream.Flush(true)更容易和更安全(https://dev59.com/OXRC5IYBdhLWcg3wMd9S#3992428)。 - Eric
文件数据被缓存在文件系统缓存中,以便写入磁盘。这些数据通常是惰性写入的,取决于磁盘写入头的位置。缓存数据量可达1GB,因此可能需要相当长的时间。如果这对您很重要,请考虑使用FileOptions.WriteThrough选项。 - MSH
有没有办法将这个逻辑应用到注册表中? - bytecode77

4

嗯,你可以关闭文件……那可能就行了。实际上,由于HAL抽象、虚拟化和磁盘硬件现在具有比几年前的 计算机 更多的处理能力和缓存内存,所以你只能希望磁盘能够完成其工作。

事务性文件系统从未真正实现 ;-p 当然,你或许可以考虑使用数据库作为后端,并使用其事务系统?

另外:请注意,并非所有流甚至保证执行Flush()——例如,GZipStream等仍保留一份未提交数据的工作缓冲区,即使刷新之后也是如此——唯一的方法是Close()它。


从技术上讲,写入数据库时不能保证停电或其他灾难性故障不会丢失写入或以某种方式损坏数据库,尽管它比简单的文件系统写入更有可能存活。 - cletus
1
可以,但是你可以将“标记为完成”和“这是结果”包装在同一事务中。 - Marc Gravell
@cletus 如果一个数据库系统不能保证这一点,那么它要么是有问题的(至少我认为不符合ACID标准的DBMS是有问题的),要么是在运行在有问题的系统上(操作系统、硬件等)。 - Paul Groke

3
我注意到.NET 4的#Flush(true)实际上并没有写入磁盘。我们遇到了一些奇怪的数据损坏问题,我在微软网站上找到了这个bug报告:bug report
该bug报告的详细信息选项卡有一个测试程序,您可以运行它以显示该问题;
  1. 将大量数据写入磁盘
  2. fs.Flush(true)。 这不需要时间(比可能写入磁盘的速度快得多)。
  3. 使用win32 API FlushFileBuffers。 这需要很长时间。
我正在改用win32 FlushFileBuffers调用...

fs.Flush(true) 对我来说完全正常。Windows 10 x64 创作者更新,.NET 4.5。 - jjxtra
@jjxtra:你测试过使用网络服务器并物理拔掉插头的情况吗? - Joshua
@Joshua 不好意思,只测试过本地文件系统,所以无法对那种情况发表评论。 - jjxtra
@jjxtra:除非您通过拔掉磁盘来导致它们失败,否则它看起来是可以工作的。您需要让网络服务器“知道”您触发了这个竞争条件。 - Joshua

0

文件数据被缓存在文件系统缓存中,以便写入磁盘。这些数据通常是惰性写入的,取决于磁盘写入头的位置。缓存数据量可达1GB,因此可能需要相当长的时间。如果这对您很重要,请考虑使用FileOptions.WriteThrough选项。


1
这已被证明也是无效的。操作系统仍将缓存。 - trevster344
@trevster344,你有这方面的来源吗? - Matt
@Matt FILE_FLAG_WRITE_THROUGH 曾经在 SATA 硬盘上存在问题:https://disruptivesql.wordpress.com/2012/05/08/sata-and-write-through/ 我不知道现在的情况如何,但在进一步研究和测试之前,我不会轻易相信它。 - Paul Groke

0
有一个简单的答案可以将缓冲区的内容刷新到磁盘。 在您的WriteAllText函数之后,打开文件,关闭它并重置它。 以下是一个例子。
My.Computer.FileSystem.WriteAllText(yourfilename, "hello", False, System.Text.Encoding.ASCII)
FileOpen(1, yourfilename, OpenMode.Input)
FileClose(1)
Reset()

-3

有太多的抽象层次,无法绝对确定数据是否被写入到磁盘,甚至到硬件层面。

这种方法并不是非常高效或百分之百可靠,但可以在文件被写入后,在另一个进程中重新打开文件并检查大小或内容,如何?


4
这样做没有意义,因为不能保证数据已经被物理写入磁盘。 - Paul Groke
2
不仅徒劳无功,而且毫无用处。重新打开请求将通过缓存层传递,就像其他任何内容一样。 - Charles Duffy

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