C.W.K.
Stream
Lesson 01 of 05 · published

Why Async Exists

~11 min · async, io-bound, concurrency

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

Threads are great for CPU-bound parallelism, but they don't scale to tens of thousands of concurrent I/O operations — each OS thread costs memory and context-switching. async/await is Rust's answer for high-concurrency I/O: thousands of tasks on a handful of threads.

The problem async solves

A web server handling 10,000 connections mostly waits — for the network, the database, the disk. Dedicating an OS thread to each idle connection wastes memory. Async lets one thread juggle thousands of tasks: while one task waits for I/O, the thread runs another. It's concurrency without a thread per task.

Cooperative, not preemptive

Async tasks are cooperative: a task runs until it hits an .await point where it's waiting on something, then voluntarily yields the thread so another task can run. There's no OS preemption between awaits. This is why async excels at I/O-bound work (lots of waiting) and is the wrong tool for CPU-bound work (no natural yield points).

Threads for CPU-bound parallelism; async for I/O-bound concurrency. If your tasks spend most of their time waiting (network, disk, timers), async scales to huge numbers cheaply. If they spend most of their time computing, threads (or a thread pool) are the right tool. Matching the model to the workload is the whole decision.

async is opt-in

Unlike some languages where everything is async, Rust keeps it opt-in and zero-cost: you only pay for async where you use it, and synchronous code stays simple. Most programs don't need async at all. You reach for it specifically when you have many concurrent I/O operations to manage.

Code

Blocking vs async: who waits, and how·rust
// Synchronous: this function blocks the thread while it 'waits'
fn fetch_blocking() -> String {
    // imagine a slow network call that ties up the whole thread
    String::from("data")
}

// Asynchronous: returns a Future; awaiting it yields the thread while waiting
async fn fetch_async() -> String {
    // a real .await here would let other tasks run during the wait
    String::from("data")
}

fn main() {
    let _f = fetch_async(); // this is a Future — not yet executed
    println!("{}", fetch_blocking());
}

External links

Exercise

List three real tasks and label each I/O-bound or CPU-bound: downloading 1000 web pages, resizing 1000 images, querying a database 1000 times. For each, decide whether async or threads fits better, and say why in one sentence.
Hint
Downloading and querying are I/O-bound (mostly waiting) -> async scales them cheaply. Resizing images is CPU-bound (mostly computing) -> threads give real parallelism across cores.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.