Async Rust Project
In this lesson, you will build a complete asynchronous Rust project that applies the async concepts you have learned so far.
This project focuses on combining tasks, channels, timeouts, and async patterns into a single working application.
Project Overview
We will build a simple asynchronous task processor that:
- Spawns multiple async workers
- Receives jobs through a channel
- Processes jobs concurrently
- Handles timeouts gracefully
This type of architecture is commonly used in servers, background job systems, and real-time applications.
Project Requirements
Before starting, ensure you have:
- Rust installed
- Cargo available
- The
tokiocrate added to your project
Create a new project using:
cargo new async_project
cd async_project
Adding Dependencies
Add Tokio as the async runtime in your Cargo.toml.
[dependencies]
tokio = { version = "1", features = ["full"] }
Project Architecture
The project consists of:
- A producer that sends jobs
- Multiple async workers
- A bounded channel for backpressure
- A timeout mechanism
Implementing the Async Workers
Each worker processes incoming jobs asynchronously.
use tokio::sync::mpsc;
use tokio::time::{sleep, Duration};
async fn worker(id: usize, mut rx: mpsc::Receiver) {
while let Some(job) = rx.recv().await {
println!("Worker {} processing job {}", id, job);
sleep(Duration::from_secs(1)).await;
}
}
Each worker waits for jobs and processes them independently.
Spawning Multiple Workers
We now spawn multiple workers using tokio::spawn.
let (tx, rx) = mpsc::channel(5);
for i in 1..=3 {
let rx_clone = rx.clone();
tokio::spawn(worker(i, rx_clone));
}
This creates three concurrent workers listening on the same channel.
Producing Jobs
The producer sends jobs into the channel.
for job in 1..=10 {
tx.send(job).await.unwrap();
}
Jobs are distributed among workers automatically.
Adding Timeouts
To prevent workers from hanging, we add a timeout.
use tokio::time::timeout;
let result = timeout(Duration::from_secs(2), async {
// simulate async work
sleep(Duration::from_secs(1)).await;
}).await;
if result.is_err() {
println!("Job timed out");
}
Timeouts ensure system responsiveness.
Full main Function
Below is the complete main function combining all parts.
#[tokio::main]
async fn main() {
let (tx, rx) = tokio::sync::mpsc::channel(5);
for i in 1..=3 {
let rx_clone = rx.clone();
tokio::spawn(worker(i, rx_clone));
}
for job in 1..=10 {
tx.send(job).await.unwrap();
}
}
What This Project Demonstrates
- Async task spawning
- Concurrency with channels
- Backpressure control
- Timeout handling
- Clean async architecture
📝 Practice Exercises
Exercise 1
Increase the number of workers to five.
Exercise 2
Add logging to track job completion time.
Exercise 3
Modify the project to drop jobs if the channel is full.
Exercise 4
Add graceful shutdown handling.
✅ Practice Answers
Answer 1
Change the worker spawn loop to run five times.
Answer 2
Record timestamps before and after processing jobs.
Answer 3
Use try_send() instead of send().
Answer 4
Use a shutdown signal and break the worker loop.
What’s Next?
In the next lesson, you will move into Rust Projects & Web Development, starting with building APIs using Rust.