在 Tokio 异步上下文进行文件读写操作时,一个常用的模块是 tokio::fs
,其中包含了标准库文件 API 的同名异步版本。
大多数文件方法都具有与标准库同名方法相同的语义,但 flush
方法稍有不同。
tokio::fs
文档开始在编写这篇文章时,Tokio 的最新版本是 1.43.0。点进文档链接可以看到该模块的简单介绍:
据文档所言,由于大多数操作系统都不提供异步的文件系统 API,tokio::fs
的异步文件操作实际上是将一般的同步文件操作放到了 spawn_blocking
的线程池里执行。
将来可能会切换到例如 io_uring
的异步文件系统 API,但至少当前在所有平台上都是使用线程池的方式实现的。
稍微往下拉一拉,我们可以看到这样的描述:
文档特别使用 Note
提示用户,写入 Tokio File
时使用 flush
很重要,因为对 write
方法的调用会在写入完成之前返回,必须显式调用 flush
才会等待写入完成。
这与标准库的 File
不同,是由 spawn_blocking
的底层实现导致的。
flush
为什么 Tokio 要额外提醒用户调用 flush
,难道标准库里没有 flush
或者不用调 flush
吗?这就要回过头来看一下标准库的文档。
还真是!由于 File
结构不包含缓冲区,Unix
和 Windows
上的 flush
方法不会做任何操作。换言之,在 99.99% 的平台上使用标准库的 File
写入而忘记 flush
不会影响任何程序逻辑。
当然,文档里也做了补充,告诉我们这只是为了帮助理解给出的底层说明,并不具有约束性,后续版本的行为也是可能变化的。
在查阅过程中我找到了一条 Tokio 主要作者 alice 在论坛的回复,或许可以帮助理解。
A completed write on a tokio::fs::File means that it has been handed off to the threadpool to be written, but that does not necessarily mean that the threadpool has written it yet. Calling flush allows you to wait for that to happen.
It's not possible to change this. The AsyncWrite trait's signature fundamentally forces us to do it this way.
对 tokio::fs::File
的写入操作完成意味着它已经被交给线程池进行写入,但这并不一定意味着线程池已经完成了写入。调用 flush
可以让你等待写入操作真正完成。
这是无法改变的。AsyncWrite
trait 的签名从根本上迫使我们以这种方式处理。
抛开细节不谈,我们可以得出一个简单结论:
写入文件不 flush
可能有错,flush
了肯定没错!
写完文件刷一下缓冲区是个好习惯,可能没效果,但肯定不会出问题。 😋