Rust supports async/await in a similar way to C# although these are supplied via runtimes, for example Tokio, async-std and others.
In this post we’ll look at the tokio runtime option.
The first thing we need to do is add tokio to the Cargo.toml, for example
[dependencies]
tokio = { version = "1", features = ["full"] }
Now, let’s create a simple async function
async fn execute() {
println!("Execution in async function");
}
Notice we do not return a Task like C# or any type in this case, but this is essentially syntactic sugar for
fn execute() -> impl Future<Output = ()>
Hence, we can see async functions return a Future (similar to a Promise in Javascript etc.).
The Future trait has a poll function which can be checked to see if the async function is ready to return a value or if it’s pending.
To await an async function we use te following syntax
execute().await;
The await will ofcourse cause the current Future to return to the caller but the code after the await will not execute until the Future completes/is ready.
If you come from C# this is much the same, i.e. running a continuation when completed etc.
To use asyc/await on main we need to make a couple of changes, first to make main async but this alone will not work without the runtime, hence main looks like this
#[tokio::main]
async fn main() {
execute().await;
}
Futures are lazy loaded. Meaning, that the future will not execute until the await is called.
As you can see Futures do not run in a thread, they are just polling futures. However we can use tokio tasks (which looks a lot like std lib threads) the execute the code on a thread
let handle = tokio::spawn(asyc move {
execute().await;
}
handle.await.unwrap();
By default tokio executes on a threadpool but we could change things, as below
#[tokio::main(flavor = "current_thread")]
Which then uses time slicing instead of threads.
Tokio is good for non blocking IO, but tokio uses a single thread for it’s main event loop hence heavy CPU will basically slow down other tasks. Hence we would need to spawn threads as already discussed.
Slight detour
As a slight detour from async/await – tokio can also create “green” threads (lightweight threads from the runtime – not OS threads), for example
async fn execute() {
time::sleep(time::Duration::from_secs(1)).await;
}
fn main() {
let runtime = tokio::runtime::Runtime::new().unwrap();
let future = execute();
runtime.block_on(future);
}