Async runtimes for Rust like tokio provide "asyncified" copies of many standard functions, including some file IO ones, which work by basically just summoning the corresponding blocking task (on a new thread?). Examples of such functions are tokio::fs::create_dir_all
, tokio::fs::read_dir
, tokio::fs::read
, ...
What's the advantage of all these functions? Why should I prefer using them over standard blocking functions in async context? If I'm .await
ing for their results, is there any gain at all?
An example would be an async web route that returns the contents of some file based on the query (using Rocket):
#[get("/foo/<query>")]
async fn foo(query: &str) -> Option<String> {
let file_path = // interpret `query` somehow and find the target file
tokio::fs::read_to_string(file_path).await.ok()
// ^ Why not just `std::fs::read_to_string(file_path).ok()`?
}
I understand the gains of async/.await
for socket IO or delayed tasks (with thread sleep), but in this case it seems pointless to me. But the opposite — this makes more complex tasks much more difficult to solve in code (working with streams when searching a file in a list of directories, for example).
.await
allows them to run while the other thread with the file IO on it is blocked. Using blocking calls in anasync
task will block the thread, thus blocking all tasks that are running on the same thread. – Aten.await
only allow other tasks to run if the task being awaited returned thePending
state last time it was polled by the executor? From what I understood from the async book, this is only returned when the task is not ready to produce any output yet, which is never the case for file IO (because the task is always ready to return some bytes, unlike, say, socket IO, whose tasks are often just waiting for new connections). If this is correct then file IO tasks never returnPending
, and are almost the same as their blocking counterparts. – Slaverystd::fs::read_to_string
will block the thread, preventing Tokio (or any other async runtime) from running any other asynchronous tasks. If you don't want to do other things while performing IO, you don't need to use async in the first place. – Cookhouseepoll()
or the like. The fact that the file descriptor is "ready" doesn't mean that reading from it won't block for the duration it takes to read the contents from disk (or, in case of network-mounted file system, from the network, which may take an arbitrary time or never finish). The async versions ofstd::fs
operations resolve that by off-loading the blocking operations to a separate thread and arranging for wakeup when the whole operation is complete. – Isoleucine