Rust Lesson 48 – Async Projects | Dataplexa

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 tokio crate 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.