tokio::fs 中 flush 方法与标准库同名方法的差异

在 Tokio 异步上下文进行文件读写操作时，一个常用的模块是 tokio::fs，其中包含了标准库文件 API 的同名异步版本。

大多数文件方法都具有与标准库同名方法相同的语义，但 flush 方法稍有不同。

从 tokio::fs 文档开始

在编写这篇文章时，Tokio 的最新版本是 1.43.0。点进文档链接可以看到该模块的简单介绍：

据文档所言，由于大多数操作系统都不提供异步的文件系统 API，tokio::fs 的异步文件操作实际上是将一般的同步文件操作放到了 spawn_blocking 的线程池里执行。

将来可能会切换到例如 io_uring 的异步文件系统 API，但至少当前在所有平台上都是使用线程池的方式实现的。

文档中 flush 方法的特别说明

稍微往下拉一拉，我们可以看到这样的描述：

文档特别使用 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 了肯定没错！

写完文件刷一下缓冲区是个好习惯，可能没效果，但肯定不会出问题。 😋