在 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
了肯定沒錯!
寫完檔案刷一下緩衝區是個好習慣,可能沒效果,但肯定不會出問題。 😋