Cancellation tokens in Rust

When using tokio::spawn we might wish to pass through a cancellation token to allow us to cancel a long running thread.

We can create a cancellation token like this

let token = CancellationToken::new();

From this we could take one or more child tokens like this

let child = token.child_token();

Using child token’s allows us to cancel all child tokens from the parent or we can cancel each one individually

Now if we spawn our threads, in this case we’ll create two concurrent branches. The first one that completes is the returning value. In this instance we’ll store the JoinHandle just to allow us to force the application to wait upon completion so we get something meaningful output to the console

let handle = tokio::spawn(async move {
  tokio::select! {
    _ = child.cancelled() => {
      println!("Child1 task cancelled");
    }
    _ = tokio::time::sleep(Duration::from_secs(30)) => {
      println!("Child2 task cancelled");
    }
  }
});

Here’s the full code, starting with cargo.toml dependencies

[dependencies]
tokio-util = "0.7.17"
tokio = { version = "1.48.0", features = ["rt", "rt-multi-thread", "macros", "time"] }
select = "0.6.1"
anyhow = "1.0.100"

Now the main.rs code

use std::io;
use std::time::Duration;
use tokio_util::sync::CancellationToken;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let token = CancellationToken::new();
    let child = token.child_token();

    let handle = tokio::spawn(async move {
        tokio::select! {
            _ = child.cancelled() => {
                println!("Child1 task cancelled");
            }
            _ = tokio::time::sleep(Duration::from_secs(30)) => {
                println!("Child2 task cancelled");
            }
        }
    });

    io::stdin().read_line(&mut String::new())?;
    token.cancel();

    handle.await.expect("Task panicked");
    println!("Task Completed");

    Ok(())
}