Course contentsShow
← Rust
Lesson 1207 of 1,91010. Concurrency PrimitivesFree lesson

Building an Async Worker Pool

You will construct a custom worker system to execute heterogeneous tasks concurrently.

In this lesson, you will learn how to design a flexible async worker pool that handles heterogeneous tasks by decoupling task submission from execution.

The Core Problem: Heterogeneity

When we talk about an "async worker pool," we aren't just creating a collection of threads. We are building a system that can accept arbitrary Future objects—tasks with different return types and logic—and schedule them across a fixed set of execution workers. In a high-performance system, we need to balance these tasks without blocking the underlying thread executor.

The Architecture

To manage heterogeneous tasks, we typically use a mpsc (multi-producer, single-consumer) channel as a task queue, where the "message" is a boxed Future.

use futures::future::BoxFuture;
use tokio::sync::mpsc;

type Task = BoxFuture<'static, ()>;

struct WorkerPool {
    sender: mpsc::Sender<Task>,
}

impl WorkerPool {
    fn spawn(&self, task: impl Future<Output = ()> + Send + 'static) {
        let _ = self.sender.try_send(Box::pin(task));
    }
}

By erasing the type of the future using BoxFuture<'static, ()>, we store fundamentally different operations (like a database query, an API call, and a file write) in the same queue.

Why Use a Pool?

If you simply tokio::spawn every task, you lose control over resource consumption. A worker pool allows you to implement backpressure. By using a bounded channel, if your workers are overwhelmed, sender.send() will naturally wait, preventing the system from ballooning in memory or hitting OOM conditions under heavy load.

Handling Execution

Each worker in your pool runs a simple loop: it continuously pulls tasks from the receiver end of the channel and .awaits them. Because we are inside an async runtime, a single worker thread can manage many of these heterogeneous futures concurrently, switching context whenever a task hits an I/O wait point. This maximizes throughput without the overhead of spawning a new OS thread per task.

Key Takeaway: A robust async worker pool treats tasks as type-erased futures, leveraging bounded channels to provide natural backpressure and prevent resource exhaustion.