Futures, Tasks, and Threads
Async programming introduces new execution units: tasks. A task is a future that the async runtime schedules, usually on a pool of operating system threads. Understanding the distinction between tasks and threads helps you design efficient systems.
Tasks vs. Threads
- Threads are managed by the operating system. Each thread has its own stack and OS scheduling overhead.
- Tasks are managed by the async runtime. Many tasks can run on a small number of threads.
Tasks are lightweight and great for I/O-bound work, while threads are better for heavy CPU-bound tasks unless you offload work to a dedicated thread pool.
Spawning Tasks
Most runtimes provide a task API. For example, with Tokio:
import tokio.task
async fn fetch(url: &str): String {
// Imagine an HTTP request here
"ok".toString()
}
async fn fetchAll(urls: Vec<String>): Vec<String> {
let handles = urls.map { url ->
task.spawn { -> fetch(&url) }
}
handles.map { handle -> await handle }
}
Each spawn call schedules a task. The runtime polls those tasks on worker
threads and wakes them when they can make progress.
When to Use Threads
If you need to run blocking or CPU-heavy work, use threads (or a dedicated blocking pool) so you don't stall the async runtime:
import std.thread
fn heavyComputation(): Int {
// CPU-intensive work
42
}
fn runInThread(): Int {
let handle = thread.spawn { -> heavyComputation() }
handle.join().unwrap()
}
Async is about waiting efficiently. Threads are about doing work in parallel. Choose the tool that matches the kind of work you are doing.