在FileStream
类中,呼叫Flush(false)
和Flush(true)
之间是有区别的,后者会呼叫cause any buffered data to be written to the file.
然而,目前还不清楚FlushAsync
的行为是什么,因为它不接受flushToDisk
参数.
我想我真正的问题是,如何确保文件以异步方式写入磁盘?
在FileStream
类中,呼叫Flush(false)
和Flush(true)
之间是有区别的,后者会呼叫cause any buffered data to be written to the file.
然而,目前还不清楚FlushAsync
的行为是什么,因为它不接受flushToDisk
参数.
我想我真正的问题是,如何确保文件以异步方式写入磁盘?
要确定. NET Core中磁盘流的实际写入/刷新行为需要深入研究源代码,因为正如Jonathan指出的,这里的文档不幸地是不准确的(或者充其量是高度误导).
首先,行为取决于FileStream
是否打开缓冲(默认值).如果没有缓冲,则在OSFileStreamStrategy中定义行为.如果它有缓冲,它将OSFileStreamStrategy
包装在BufferedFileStreamStrategy中.
从不太常见但更简单的无缓冲情况开始,请参阅FlushAsync覆盖:
public sealed override Task FlushAsync(CancellationToken cancellationToken) =>
Task.CompletedTask; // no buffering = nothing to flush
事实证明,在非缓冲的情况下,FlushAsync
是一个无操作.字节已离开进程,现在掌握在操作系统手中,但操作系统尚未被独立告知将字节刷新到磁盘.
现在请注意该文件中紧随其后的几行:
internal sealed override void Flush(bool flushToDisk)
{
if (flushToDisk && CanWrite)
{
FileStreamHelpers.FlushToDisk(_fileHandle);
}
}
因此,不仅FlushAsync
总是无缓冲的无操作,而且同步Flush
方法同样也是无操作,除非指定flushToDisk
.这当然会迫使随后的呼叫Win32 FlushFileBuffers
或Unix的fsync
.因此,在非缓冲的情况下,问题的简单答案是否定的,没有办法要求OS在关闭流之前将字节写入物理磁盘而不使用同步Flush(bool flushToDisk)
过载.
关于更常见的缓冲情况,参见FlushAsyncInternal.你会注意到所有这些做的是拨打OSFileStreamStrategy.WriteAsync
,即,它会刷新其内部缓冲区并将字节发送到操作系统,就像它没有缓冲一样,但它不会进一步要求操作系统确保它们被写入磁盘.
在缓冲情况下,同步Flush(bool flushToDisk)
overload也将其缓冲器刷新到OS,除了同步.然后,它进一步调用OSFileStreamStrategy.Flush(flushToDisk)
——如果flushToDisk
为真,它本身就是一个无操作.
所以所有这些都是一个冗长的说法,在所有情况下,FlushAsync
只保证字节已发送到操作系统,但不保证操作系统已将这些字节写入磁盘.如果你想让操作系统这样做,你需要调用同步Flush(bool flushToDisk)
重载.(我说"询问"是因为,即使这样,也永远不能FlushAsync
%保证字节在硬件上,特别是在NAS情况下).
因此,如果你想尽可能保持异步,最好的方法是这样写:
await stream.FlushAsync();
await Task.Run(() => stream.Flush(true));
这两行其实并不多余!如果先调用stream.Flush(true)
而不调用await stream.FlushAsync()
,前者会同步发送数据到操作系统,阻塞线程,而不是使用更有效的异步操作系统写函数.如果你不使用第二行,操作系统将不会被告知将自己的缓冲区刷新到磁盘.
不幸的是,FlushFileBuffers
和fsync
都是同步的,所以你会希望把stream.Flush(true)
包装在Task.Run
中,以避免阻塞调用线程,但是在硬件级别上,没有办法以真正异步的方式做到这一点(这是相当不幸的,因为在那个级别上,写入磁盘当然是异步发生的).