tokio::select with an explicit cancellation token
I used to wire up my own oneshot::channel for shutdown every time. Then I started using tokio_util::sync::CancellationToken and never went back. It supports multiple clones, child tokens that cancel when the parent does, and a natural fit with tokio::select!.
use tokio::time::{sleep, Duration};
use tokio_util::sync::CancellationToken;
async fn worker(id: usize, cancel: CancellationToken) {
loop {
tokio::select! {
_ = cancel.cancelled() => {
tracing::info!(id, "shutting down");
return;
}
_ = sleep(Duration::from_secs(1)) => {
tracing::debug!(id, "tick");
// real work
}
}
}
}
#[tokio::main]
async fn main() {
let root = CancellationToken::new();
let mut handles = Vec::new();
for id in 0..4 {
let c = root.child_token();
handles.push(tokio::spawn(worker(id, c)));
}
tokio::signal::ctrl_c().await.ok();
root.cancel();
for h in handles { let _ = h.await; }
}
Two things worth knowing:
child_token()means you can cancel a subtree without cancelling the root. I use this to drop a single tenant’s background jobs without disturbing the rest.cancelled()returns a future, not a&mut, so you can put it in aselect!without borrowing headaches.