Rust async programming with tokio
Project page: tokio.rs
Introduction
Provides a multi-threaded runtime for executing async code, as well as an async version of the standard library
It is based on Rust await and async feature.
When not to use Tokio
- Tokio is designed for IO-bound applications where applications spend most of time waiting for IO. For CPU-bound applications, use Rust rayon.
- Reading a lot of files – no advantage compared to an ordinary threadpool.
- Sending a single web request.
What is async programming?
When encountering an operation that cannot be completed immediately, the program blocks until the operation completes. In the case of async programming, operations that cannot complete immediately are suspended in the background. When the operation is done, the task is unsuspended and the program continues.
#[tokio::main]
async fn main() {
}
// Gets transformed into
fn main() {
let mut rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
println!("hello");
})
}
tokio::net::TcpListener
Tokio allows async code to work on many tasks concurrently, sometimes on a single thread.
Spawning tasks
tokio::spawn(async move {
process(socket).await;
});
Tasks are async green threads. spawn
returns a JoinHandle
with which the caller can interact with the thread. await
ing the JoinHandle
gets the thread’s return value.
let handle = tokio::spawn(async {
42
});
let out = handle.await.unwrap();
The type lifetime of a spawned task must be 'static
. Spawned tasks cannot contain references to data owned outside the task.
This doesn’t compile because variables are not moved into async blocks by default.
let v = vec![1, 2, 3];
task::spawn(async {
println!("Here's a vec: {:?}", v);
});
// Fix
task::spawn(async move { ... });
Data sharing is enabled by synchronization primitives such as Arc
.
Spawned task must implement Send
so the runtime can move tasks between threads. They are Send
when all data held across await
calls is Send
.
// This works
#[tokio::main]
async fn main() {
tokio::spawn(async {
// The scope forces `rc` to drop before `.await`.
{
let rc = Rc::new("hello");
println!("{}", rc);
}
// `rc` is no longer used. It is **not** persisted when
// the task yields to the scheduler
yield_now().await;
});
}
// This doesn't work
#[tokio::main]
async fn main() {
tokio::spawn(async {
let rc = Rc::new("hello");
// `rc` is used after `.await`. It must be persisted to
// the task's state.
yield_now().await;
println!("{}", rc);
});
}