Explanation
What just happened
#[tokio::main(flavor = "current_thread")] runs every task on one thread. Each task printed a step, then yield_now().await handed the thread back to Tokio, which ran another ready task. They took turns, and every switch happened at an .await.
This run printed a1 b1 b2 a2 a3 b3. b got two turns in a row: which ready task goes next is the scheduler’s choice.
Try the what-if switch
- no .await: task a’s loop never awaits, so it never gives the thread back. It printed all three steps before b started.
Why
Rust’s async is cooperative. A future runs until it returns Pending at an .await, and only then can the runtime run something else. Nothing interrupts a task in the middle.
Watch out
A long loop with no .await holds the thread just like a blocking call. See the next scenario.
Common questions
When does a Tokio task give up the thread?
Only at an .await that isn't ready yet, like yield_now().await or a sleep. Between two awaits, the task runs without interruption.
Do spawned tasks run in parallel?
Not on a current_thread runtime: one thread runs every task, so only one runs at a time. On the default multi-threaded runtime they can run on different worker threads.
In what order do ready tasks run?
That's up to Tokio's scheduler. Each task's own steps stay in order, but don't depend on which task goes next.
Verified: this timeline matches a real program, checked in CI on Rust 1.99 with Tokio 1.54. How we verify. Unclear or wrong? Tell us