/hello-tokio-async-rust

A learning project exploring Tokio async runtime concepts in Rust through practical examples. Each binary demonstrates a specific async programming concept, building complexity sequentially.

Primary LanguageRust

Hello Tokio Async Rust

A learning project exploring Tokio async runtime concepts in Rust through practical examples. Each binary demonstrates a specific async programming concept, building complexity sequentially.

🚀 What is Tokio?

Tokio is an asynchronous runtime for Rust that provides the building blocks for writing fast, reliable, and scalable network applications. It's built on top of Rust's async/await syntax and provides:

  • Async runtime: Efficiently manages thousands of concurrent tasks
  • Async I/O: Non-blocking file and network operations
  • Concurrency primitives: Channels, mutexes, and other synchronization tools
  • Timers: Async sleep and interval functionality

📚 Learning Path

This project follows a sequential learning approach, with each binary building upon previous concepts:

1. Basic Async Tasks - concurrent_tasks

  • File: src/bin/concurrent_tasks.rs
  • Concept: Spawning concurrent async tasks
  • Key Learning: How to run multiple async functions concurrently without blocking
  • Run: cargo run --bin concurrent_tasks

2. Async Sleep & Timers - sleep

  • File: src/bin/sleep.rs
  • Concept: Async sleep vs blocking sleep, background tasks
  • Key Learning: Difference between tokio::time::sleep() and std::thread::sleep()
  • Run: cargo run --bin sleep

3. Async File Operations - read_file & write_file

  • Files:
    • src/bin/read_file.rs
    • src/bin/write_file.rs
  • Concept: Non-blocking file I/O operations
  • Key Learning: Using tokio::fs for async file operations
  • Run:
    • cargo run --bin read_file
    • cargo run --bin write_file

4. Shared State & Mutexes - shared_state

  • File: src/bin/shared_state.rs
  • Concept: Async-safe mutexes and shared state management
  • Key Learning: Difference between std::sync::Mutex and tokio::sync::Mutex
  • Run: cargo run --bin shared_state

5. Channels & Communication - channels

  • File: src/bin/channels.rs
  • Concept: Multi-producer, single-consumer channels for task communication
  • Key Learning: Using mpsc::channel for async message passing between tasks
  • Run: cargo run --bin channels

6. Timeouts & Deadlines - timeout

  • File: src/bin/timeout.rs
  • Concept: Setting time limits for async operations
  • Key Learning: Using tokio::time::timeout to prevent operations from hanging indefinitely
  • Run: cargo run --bin timeout

7. Periodic Tasks & Intervals - ticker

  • File: src/bin/ticker.rs
  • Concept: Running tasks at regular intervals
  • Key Learning: Using tokio::time::interval for periodic async task execution
  • Run: cargo run --bin ticker

8. Custom Futures & Wakers - delay

  • File: src/bin/delay.rs
  • Concept: Implementing custom futures with manual polling
  • Key Learning: Understanding Future trait, Poll, wakers, and how async/await works under the hood
  • Run: cargo run --bin delay

9. One-Shot Notifications - notify

  • File: src/bin/notify.rs
  • Concept: Signaling between tasks using one-shot notifications
  • Key Learning: Using tokio::sync::Notify for simple task coordination without data transfer
  • Run: cargo run --bin notify

10. Select & Racing Operations - select

  • File: src/bin/select.rs
  • Concept: Racing multiple async operations and handling the first to complete
  • Key Learning: Using tokio::select! macro to wait on multiple futures simultaneously
  • Run: cargo run --bin select

11. One-Shot Channels - one_shot_channel

  • File: src/bin/one_shot_channel.rs
  • Concept: Single-use channels for one-time value transfer between tasks
  • Key Learning: Using tokio::sync::oneshot for simple request-response patterns
  • Run: cargo run --bin one_shot_channel

12. Async Streams - stream

  • File: src/bin/stream.rs
  • Concept: Processing sequences of async values with streams
  • Key Learning: Using tokio_stream for handling collections of async data
  • Run: cargo run --bin stream

13. Bridging Async & Sync - bridge_async_in_sync

  • File: src/bin/bridge_async_in_sync.rs
  • Concept: Running async code from synchronous contexts
  • Key Learning: Using tokio::runtime::Builder to manually create and manage Tokio runtimes
  • Run: cargo run --bin bridge_async_in_sync

14. Signal Handling - signal

  • File: src/bin/signal.rs
  • Concept: Handling system signals asynchronously
  • Key Learning: Using tokio::signal for graceful shutdown and signal processing
  • Run: cargo run --bin signal

15. Cancellation Tokens - cancellation_token

  • File: src/bin/cancellation_token.rs
  • Concept: Cooperative cancellation of async tasks
  • Key Learning: Using tokio_util::sync::CancellationToken for graceful task termination
  • Run: cargo run --bin cancellation_token

16. Task Tracking & Management - task_tracker

  • File: src/bin/task_tracker.rs
  • Concept: Tracking and waiting for multiple spawned tasks to complete
  • Key Learning: Using tokio_util::task::TaskTracker for managing task lifecycle and completion
  • Run: cargo run --bin task_tracker

17. Testing Async Code - test_async

  • File: src/bin/test_async.rs
  • Concept: Writing unit tests for async functions
  • Key Learning: Using #[tokio::test] attribute to test async code with assertions
  • Run: cargo test --bin test_async

18. Task Tracking with Cancellation - task_tracker_with_cancellation

  • File: src/bin/task_tracker_with_cancellation.rs
  • Concept: Combining task tracking with cooperative cancellation
  • Key Learning: Using TaskTracker and CancellationToken together for managing cancellable task groups
  • Run: cargo run --bin task_tracker_with_cancellation

19. Lazy Async Initialization - once_cell

  • File: src/bin/once_cell.rs
  • Concept: One-time async initialization of static values
  • Key Learning: Using tokio::sync::OnceCell for lazy initialization that happens only once, even with concurrent access
  • Run: cargo run --bin once_cell

🛠️ Project Structure

src/
├── bin/                    # Binary examples
│   ├── concurrent_tasks.rs # Basic async task spawning
│   ├── sleep.rs           # Async timers and sleep
│   ├── read_file.rs       # Async file reading
│   ├── write_file.rs      # Async file writing
│   ├── shared_state.rs    # Async mutexes and shared state
│   ├── channels.rs        # Async channels for communication
│   ├── timeout.rs         # Timeout handling for async operations
│   ├── ticker.rs          # Periodic task execution with intervals
│   ├── delay.rs           # Custom Future implementation with manual polling
│   ├── notify.rs          # One-shot notifications between tasks
│   ├── select.rs          # Racing async operations with select! macro
│   ├── one_shot_channel.rs # Single-use channels for one-time value transfer
│   ├── stream.rs          # Processing sequences of async values with streams
│   ├── bridge_async_in_sync.rs # Running async code from synchronous contexts
│   ├── signal.rs          # Handling system signals asynchronously
│   ├── cancellation_token.rs # Cooperative cancellation of async tasks
│   ├── task_tracker.rs    # Tracking and waiting for multiple spawned tasks
│   ├── test_async.rs      # Writing unit tests for async functions
│   ├── task_tracker_with_cancellation.rs # Combining task tracking with cancellation
│   └── once_cell.rs       # Lazy async initialization of static values
├── lib.rs                 # Library entry point (exposes utils module)
├── main.rs                # Main binary entry point
└── utils.rs               # Shared utilities including tracing logger setup

🚦 Getting Started

Prerequisites

  • Rust 1.80+ with Cargo
  • Basic understanding of Rust syntax and async/await

Running Examples

# Clone and navigate to project
cd hello-tokio-async-rust

# Run any specific example
cargo run --bin concurrent_tasks
cargo run --bin sleep
cargo run --bin read_file
cargo run --bin write_file
cargo run --bin shared_state
cargo run --bin channels
cargo run --bin timeout
cargo run --bin ticker
cargo run --bin delay
cargo run --bin notify
cargo run --bin select
cargo run --bin one_shot_channel
cargo run --bin stream
cargo run --bin bridge_async_in_sync
cargo run --bin signal
cargo run --bin cancellation_token
cargo run --bin task_tracker
cargo run --bin task_tracker_with_cancellation
cargo run --bin once_cell

# Run async tests
cargo test --bin test_async

🎯 Key Concepts Covered

Async/Await Basics

  • Understanding async fn and .await
  • The #[tokio::main] macro
  • Task spawning with tokio::spawn

Concurrency vs Parallelism

  • Running multiple async tasks concurrently
  • Non-blocking operations
  • Task handles and awaiting completion

Async I/O

  • File operations with tokio::fs
  • Non-blocking I/O operations
  • Error handling in async contexts

Synchronization

  • Async-safe mutexes (tokio::sync::Mutex)
  • When to use async vs standard mutexes
  • Shared state management across tasks

Communication

  • Multi-producer, single-consumer channels
  • Task-to-task communication
  • Channel capacity and backpressure

Timeout Handling

  • Setting deadlines for async operations
  • Preventing operations from hanging indefinitely
  • Using tokio::time::timeout for graceful failure handling

Task Coordination

  • One-shot notifications between tasks
  • Simple signaling without data transfer
  • Using tokio::sync::Notify for lightweight task coordination
  • Racing multiple futures with tokio::select!
  • Handling the first completed operation among many
  • Single-use channels for one-time value transfer
  • Request-response patterns with tokio::sync::oneshot

Stream Processing

  • Processing sequences of async values
  • Using tokio_stream for handling collections of async data
  • Stream iteration with StreamExt::next()
  • Converting iterators to async streams

Runtime Management

  • Manual runtime creation and management
  • Bridging async and synchronous code
  • Using tokio::runtime::Builder for custom runtime configuration
  • Running async code from non-async contexts

Signal Handling

  • Handling system signals asynchronously
  • Graceful shutdown and cleanup
  • Using tokio::signal for Ctrl+C and other signal processing
  • Cross-platform signal handling

Task Cancellation

  • Cooperative cancellation of async tasks
  • Using tokio_util::sync::CancellationToken for graceful termination
  • Combining cancellation with tokio::select! for responsive task management
  • Clean shutdown patterns for long-running operations

Task Management

  • Tracking multiple spawned tasks with TaskTracker
  • Managing task lifecycle and completion
  • Using tracker.close() and tracker.wait() for coordinated task shutdown
  • Bulk task management for complex async workflows
  • Combining TaskTracker with CancellationToken for cancellable task groups
  • Using tokio::select! to race between task execution and cancellation signals

Testing Async Code

  • Writing unit tests for async functions
  • Using #[tokio::test] attribute for async test execution
  • Testing async operations with assertions
  • Ensuring async code correctness through automated tests

Lazy Initialization

  • One-time async initialization of static values
  • Using tokio::sync::OnceCell for lazy initialization
  • Thread-safe initialization that happens only once, even with concurrent access
  • Deferring expensive async operations until first access

🔍 Understanding the Examples

Why Sequential Learning?

Each example builds upon the previous one:

  1. Start simple with basic task spawning
  2. Add timing to understand async vs blocking operations
  3. Introduce I/O to see async file operations
  4. Handle state to learn about async-safe synchronization
  5. Communicate between tasks using channels
  6. Add timeouts to prevent operations from hanging indefinitely
  7. Create intervals to run periodic async tasks
  8. Implement Futures to understand low-level async mechanics
  9. Coordinate tasks using lightweight notifications
  10. Race operations to handle the first completed future
  11. Transfer values using single-use channels
  12. Process sequences of async values with streams
  13. Bridge contexts between async and synchronous code
  14. Handle signals for graceful shutdown and system interaction
  15. Cancel tasks cooperatively for responsive application control
  16. Track tasks to manage multiple spawned operations and wait for completion
  17. Test async code to ensure correctness and reliability
  18. Combine patterns by integrating task tracking with cancellation for robust task management
  19. Initialize lazily with async one-time initialization for static values

Common Patterns

  • Task spawning: tokio::spawn(async { ... })
  • Async sleep: tokio::time::sleep(Duration::from_secs(n)).await
  • File operations: tokio::fs::read_to_string() and tokio::fs::write()
  • Async mutexes: tokio::sync::Mutex with .lock().await
  • Channels: mpsc::channel(n) for sender/receiver communication
  • Timeouts: tokio::time::timeout(Duration::from_secs(n), future).await
  • Intervals: tokio::time::interval(Duration::from_secs(n)) for periodic tasks
  • Custom Futures: Implement Future trait with poll() method and waker handling
  • Notifications: tokio::sync::Notify for one-shot task signaling
  • Select macro: tokio::select! for racing multiple async operations
  • One-shot channels: tokio::sync::oneshot::channel() for single-use value transfer
  • Streams: tokio_stream::iter() and StreamExt::next() for processing async sequences
  • Runtime bridging: tokio::runtime::Builder::new_multi_thread().build().unwrap().block_on() for running async code from sync contexts
  • Signal handling: tokio::signal::ctrl_c().await for graceful shutdown and signal processing
  • Cancellation: CancellationToken::new() and token.cancelled().await for cooperative task cancellation
  • Task tracking: TaskTracker::new() and tracker.spawn() for managing multiple task lifecycles
  • Combined patterns: Using TaskTracker with CancellationToken and tokio::select! for cancellable task groups
  • Lazy initialization: OnceCell::const_new() and .get_or_init() for one-time async initialization of static values
  • Async testing: #[tokio::test] attribute for writing unit tests for async functions

📊 Tracing & Logging

This project includes a structured logging setup using the tracing ecosystem:

Utilities Module (src/utils.rs)

The get_logger() function provides a pre-configured tracing subscriber with:

  • Compact format for cleaner log output
  • File paths and line numbers for easy debugging
  • Thread IDs to track concurrent operations
  • No target paths for reduced clutter

Usage in Binaries

All binary examples can use the shared logger configuration:

use hello_tokio_async_rust::utils::get_logger;

#[tokio::main]
async fn main() {
    let sub = get_logger();
    tracing::subscriber::set_global_default(sub).unwrap();

    tracing::info!("Application started");
}

The lib.rs file exposes the utils module, making it accessible to all binary files in the project.

🤝 Contributing

This is a personal learning project, but feel free to:

  • Suggest improvements to examples
  • Add new async concepts
  • Improve documentation
  • Report issues or bugs

📚 Resources


Happy Learning! 🦀✨

This project demonstrates the power and elegance of async Rust with Tokio, showing how to build efficient, concurrent applications without the complexity of manual thread management.