tokio::task::spawn_blocking runs synchronous, potentially blocking code on Tokio’s dedicated blocking-thread queue and returns a future that can be awaited. It is the preferred bridge when an async context must call a crate without async support, such as a database driver or networking library. section 8.7
Read a file without blocking the async path
Write the file through tokio::fs, then move synchronous std::fs::read_to_string into spawn_blocking: tokio::task::spawn_blocking(|| read_file(filename)).await??. The returned future is awaited like ordinary async code, while the read executes on a Tokio-managed blocking thread. Tokio’s runtime builder can configure the number of blocking threads. section 8.7
use tokio::io::{self, AsyncWriteExt};
async fn write_file(filename: &str) -> io::Result<()> { #1
let mut f = tokio::fs::File::create(filename).await?;
f.write(b"Hello, file!").await?;
f.flush().await?;
Ok(())
}
fn read_file(filename: &str) -> io::Result<String> { #2
std::fs::read_to_string(filename)
}
#[tokio::main]
async fn main() -> io::Result<()> {
let filename = "mixed-sync-async.txt";
write_file(filename).await?;
let contents =
tokio::task::spawn_blocking(|| read_file(filename)).await??; #3
println!("File contents: {}", contents);
tokio::fs::remove_file(filename).await?;
Ok(())
}
Compare asynchronous file writing with synchronous reading delegated to spawn_blocking, including nested error propagation with .await??. section 8.7
