๐ŸŽ“ A1Learner
๐Ÿ”

Rust Tokio Time

Tokio time is a set of timers that pause one task, not a whole thread. sleep pauses a task for one span of time.

sleep pauses one task

tokio::time::sleep pauses the task, not the thread โ€” the runtime stays free for other tasks while this one waits:

sleep(Duration::from_millis(50)).await;
println!("woke up after 50 ms");

A sleep does nothing until you await it

sleep is a future: the pause happens at the .await, not when the future is made:

let nap = sleep(Duration::from_millis(50)); // nothing happens yet
nap.await; // the pause happens here

interval ticks at a steady rhythm

Use interval for repeating work. If your code falls behind, the default behaviour is to fire the missed ticks back-to-back until it catches up:

let mut ticker = interval(Duration::from_millis(20));
for _ in 0..3 {
    ticker.tick().await;
    println!("tick");
}

timeout puts a deadline around a job

If the future is too slow, timeout returns Err โ€” and the future is dropped:

let result = timeout(Duration::from_millis(10), sleep(Duration::from_secs(5))).await;
match result {
    Ok(()) => println!("finished in time"),
    Err(_) => println!("too slow - gave up"),
}

An Instant plus a Duration is a deadline

Instant is Tokio's clock value. Add a Duration to now, and you have the moment by which a job must finish:

let deadline = Instant::now() + Duration::from_millis(100);
println!("deadline set = {}", deadline > Instant::now());

sleep_until waits for a moment

sleep waits for a span of time. sleep_until waits until a moment. That is the waiting half of a deadline:

let moment = Instant::now() + Duration::from_millis(30);
sleep_until(moment).await;
println!("the moment arrived");

Example

// Cargo.toml: tokio = { version = "1", features = ["full"] }
use tokio::time::{sleep, timeout, Duration};

#[tokio::main]
async fn main() {
    sleep(Duration::from_millis(50)).await;
    println!("woke up after 50 ms");

    let slow = timeout(Duration::from_millis(10), sleep(Duration::from_secs(5))).await;
    match slow {
        Ok(()) => println!("finished in time"),
        Err(_) => println!("too slow - gave up after 10 ms"),
    }
}

Key points

  • Timers cost almost nothing: one runtime can hold thousands of sleeping tasks
  • Choose what a missed tick means with set_missed_tick_behavior.
  • Burst fires the missed ticks until caught up.
  • That is the default.
  • Skip drops them and waits for the next scheduled tick.
  • Delay waits a full period from the late tick.

In this example, the first sleep pauses the task for 50 milliseconds while the runtime stays free for other work. Then timeout wraps a five-second sleep in a ten-millisecond deadline. The deadline wins, timeout returns Err, and the long sleep is dropped โ€” the program does not wait five seconds.

Note

Never use std::thread::sleep inside a task โ€” it blocks the worker thread for everyone, as the Blocking lesson showed.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Rust Playground

Exercise

timeout(10ms, job).await returns Err. What happened to job?