Rust Tokio Tasks
A Tokio task is a lightweight job run by the runtime. A task costs little more than a future, not a whole operating-system thread.
Spawn a task
spawn starts the job at once and hands back a JoinHandle:
let handle = tokio::spawn(async { 40 + 2 });Collect the result with await
Awaiting the handle hands back whatever the task returned:
let answer = handle.await.unwrap();
println!("answer = {answer}");Run two futures in one task
join! runs futures side by side inside one task and waits for both:
let (one, two) = tokio::join!(
async { "one".len() },
async { "two".len() }
);
println!("{} and {}", one, two);Stop a task from a second handle
The JoinHandle stays with you for the verdict. The AbortHandle goes to someone else. It goes to whoever owns the stop button:
let task = tokio::spawn(async {
sleep(Duration::from_secs(60)).await;
9
});
let aborter = task.abort_handle();
aborter.abort();
match task.await {
Ok(_) => println!("finished normally"),
Err(e) => println!("cancelled = {}", e.is_cancelled()),
}Warn the runtime before blocking
On a multi-thread runtime, block_in_place moves your other tasks out of the way before the blocking call runs. The call itself is ordinary sync code:
let sum = tokio::task::block_in_place(|| (1..=100u64).sum::<u64>());
println!("sum = {sum}");Example
// Cargo.toml: tokio = { version = "1", features = ["full"] }
#[tokio::main]
async fn main() {
let first = tokio::spawn(async { "one".len() });
let second = tokio::spawn(async { "two".len() });
let third = tokio::spawn(async { "three".len() });
let total = first.await.unwrap() + second.await.unwrap() + third.await.unwrap();
println!("total letters = {}", total);
}Key points
- Await the JoinHandle to get the task's value.
- It gives a Result.
- A task can panic
- A task must be Send + 'static: the runtime may move it between threads, and it must own what it uses
- Tasks are cheap: each one costs little more than a future, not a whole OS thread
- A JoinError tells you how a task ended: err.is_cancelled() is true after an abort, err.is_panic() after a panic โ awaiting an aborted task gives you an Err to inspect, not a panic of your own
- tokio::task::block_in_place(|| ...) tells a multi-thread runtime one thing.
- It says "this thread is about to block".
- The runtime reacts first.
- It hands the thread's other tasks to a different thread.
- It panics on a current-thread runtime โ spawn_blocking stays the everyday answer
- A program can run thousands of tasks at once.
In this example, three tasks are spawned, each counting the letters of one word. Each spawn returns a JoinHandle at once, so all three jobs are in flight together. Awaiting the handles one by one collects 3, 3, and 5. The sum is 11 โ and the awaits did not slow the tasks down, because the work was already running.
Note
Dropping a JoinHandle does not stop the task โ it keeps running, detached.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท Rust Playground
Exercise
You spawn a task and drop its JoinHandle without awaiting it. What happens?