A comprehensive educational project demonstrating advanced multithreading, asynchronous programming, and concurrency concepts in C# using .NET 8.0. This repository contains practical examples organized into modules, each focusing on specific threading and async patterns.
- Overview
- Prerequisites
- Installation
- Project Structure
- Modules Overview
- Main Program - Delegate-Based Thread Communication
- Module 1 - Getting Started
- Module 2 - Thread Joining and Cancellation
- Module 3 - Thread Synchronization
- Module 4 - ThreadPool
- Module 5 - Async/Await Fundamentals
- Module 6 - Async Streams
- Module 7 - Task Methods and Properties
- Module 8 - ValueTask Optimization
- Module 9 - Task-Based Asynchronous Pattern (TAP)
- Module 10 - Deadlock Prevention and Best Practices
- Key Concepts Covered
- Running the Examples
- Best Practices Demonstrated
- Contributing
- License
This project serves as a comprehensive learning resource for understanding multithreading and asynchronous programming in C#. It progresses from basic thread operations to advanced async patterns, providing hands-on examples of:
- Thread Management: Creating, starting, joining, and cancelling threads
- Thread Synchronization: Locks, mutexes, semaphores, and monitors
- Thread Pooling: Efficient thread management using ThreadPool
- Asynchronous Programming: async/await, Tasks, and ValueTask
- Advanced Patterns: TAP (Task-based Asynchronous Pattern), async streams, and async initialization
- Best Practices: Deadlock prevention, exception handling, and performance optimization
- .NET 8.0 SDK or later
- Visual Studio 2022 (recommended) or Visual Studio Code with C# extension
- Basic understanding of C# programming
- Familiarity with object-oriented programming concepts
-
Clone the repository
git clone https://github.com/intisor/MultiThread.git cd MultiThread -
Restore dependencies
dotnet restore
-
Build the solution
dotnet build
MultiThread/
βββ Program.cs # Main program with delegate-based thread communication
βββ MultiThreadPrac.csproj # Main project file
βββ MultiThreadPrac.sln # Solution file
βββ Module1/ # Hello World starter
βββ Module2/ # Thread joining and cancellation
βββ Module3/ # Thread synchronization
βββ Module4/ # ThreadPool implementation
βββ Module5/ # Async/Await fundamentals
βββ Module6/ # Async streams
βββ Module7/ # Task methods and properties
βββ Module8/ # ValueTask optimization
βββ Module9/ # Task-based Asynchronous Pattern (TAP)
βββ Module10/ # Deadlock prevention and best practices
Each module is a self-contained C# console application targeting .NET 8.0.
Location: Program.cs
Concept: Demonstrates how to retrieve data from a thread function using delegates.
Key Features:
- Custom delegate definition (
SumOfNumberCallDelegate) - Thread callback mechanism
- Passing data between threads via delegates
- Helper class pattern for thread management
What it does:
- Calculates cumulative sum of numbers (0 to n-1)
- Reports each intermediate sum back to the main thread via delegate callback
- Demonstrates decoupling between thread execution and result consumption
Code Highlights:
// Delegate for thread communication
public delegate void SumOfNumberCallDelegate(int SumOfNum);
// Helper class manages thread work and callbacks
public class Helper
{
private int Number;
SumOfNumberCallDelegate _CallDelegate;
public void ShowNumbers()
{
int sum = 0;
for (var i = 0; i < Number; i++)
{
sum += i;
_CallDelegate?.Invoke(sum); // Callback to main thread
}
}
}Run:
dotnet run --project MultiThreadPrac.csprojLocation: Module1/Program.cs
Concept: Basic "Hello, World!" introduction to the project structure.
Purpose: Serves as a template and starting point for understanding the module structure.
Run:
dotnet run --project Module1/Module1.csprojLocation: Module2/Program.cs
Concept: Advanced thread lifecycle management with joining and cancellation tokens.
Key Features:
CancellationTokenandCancellationTokenSourceusage- Thread joining with
Join()method - Synchronized vs asynchronous thread execution
- Handling cancellation requests gracefully
What it demonstrates:
- Creating multiple threads with different start methods
- Using
CancellationTokento signal thread cancellation Join()to wait for thread completion before proceeding- Ordinal number formatting helper method
Key Code:
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Thread t1 = new Thread(new ParameterizedThreadStart(Method1));
t1.Start(token);
// Cancel the token
cts.Cancel();
// Wait for t2 to complete before starting t3
t2.Join();Run:
dotnet run --project Module2/Module2.csprojLocation: Module3/Program.cs
Concept: Securing shared resources using various synchronization mechanisms.
Key Features:
- Lock: Simple mutual exclusion
- Semaphore: Controlling access to a limited number of resources
- Monitor: Advanced synchronization with wait/pulse capabilities
- Mutex: Cross-process synchronization
- ReaderWriterLockSlim: Optimized read-heavy scenarios
What it demonstrates:
- Protecting shared counter variable from race conditions
- Using
lockkeyword for critical sections Semaphoreto limit concurrent thread access (2 threads at a time)- Thread naming and identification
- Preventing data corruption in multithreaded environments
Key Code:
private static int _counter = 0;
private static readonly object _lock = new object();
private static Semaphore _semaphore = new Semaphore(2, 2); // Max 2 concurrent threads
private static void IncrementCounter()
{
_semaphore.WaitOne(); // Acquire semaphore
for (int i = 1; i <= 5; i++)
{
lock (_lock) // Protect shared resource
{
_counter++;
}
Console.WriteLine($"{Thread.CurrentThread.Name} : {i}");
}
_semaphore.Release(); // Release semaphore
}Run:
dotnet run --project Module3/Module3.csprojLocation: Module4/Program.cs
Concept: Efficient thread management using the .NET ThreadPool.
Key Features:
- Configuring ThreadPool min/max threads
- Queueing work items to ThreadPool
- Automatic thread reuse and management
- Performance benefits over manual thread creation
What it demonstrates:
- Reading and setting ThreadPool configuration
ThreadPool.QueueUserWorkItem()for task scheduling- Thread pool thread identification
- Simulating concurrent work with random delays
Key Code:
// Configure thread pool
ThreadPool.SetMaxThreads(10, maxThreads);
ThreadPool.SetMinThreads(7, minThreads);
// Queue tasks
for (int i = 1; i < 20; i++)
{
int taskNumber = i;
ThreadPool.QueueUserWorkItem(Task, taskNumber);
}
static void Task(object state)
{
int taskNumber = (int)state;
Console.WriteLine($"Task {taskNumber} on thread {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(new Random().Next(100, 1000));
}Run:
dotnet run --project Module4/Module4.csprojLocation: Module5/Program.cs
Concept: Introduction to asynchronous programming with async/await pattern.
Key Features:
async Taskmethod declarationawaitkeyword for asynchronous operationsTask.Delay()for non-blocking delays- Exception handling in async methods
- Async method chaining
What it demonstrates:
- Making coffee asynchronously (heating water, mixing ingredients)
- Sequential async operations with proper awaiting
- Try-catch exception handling in async context
- Returning values from async methods
Key Code:
private static async Task Main(string[] args)
{
try
{
Coffee coffee = new Coffee();
string heatwater = await coffee.HeatWaterAsync(2);
Console.WriteLine(heatwater);
await coffee.MixAsync(2, 4);
Console.WriteLine("Breakfast Completed");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
}
public async Task<string> HeatWaterAsync(int temp)
{
Console.WriteLine($"Heating water at {temp}Β°C");
await Task.Delay(3000); // Non-blocking delay
return "Water has been heated";
}Run:
dotnet run --project Module5/Module5.csprojLocation: Module6/Program.cs
Concept: Asynchronous iteration using IAsyncEnumerable<T> and await foreach.
Key Features:
IAsyncEnumerable<T>for async sequencesyield returnin async methodsawait foreachfor consuming async streams- Streaming data production and consumption
What it demonstrates:
- Producing numbers asynchronously with delays
- Consumer-producer pattern with async streams
- Memory-efficient streaming of data
- Asynchronous iteration pattern
Key Code:
public async IAsyncEnumerable<int> ProduceNumberAsync()
{
for (var i = 1; i <= 10; i++)
{
await Task.Delay(5000); // Simulate async work
yield return i; // Stream each number
}
}
public async Task ConsumeNumberAsync()
{
await foreach (var item in ProduceNumberAsync())
{
Console.WriteLine(item); // Process each item as it arrives
}
}Run:
dotnet run --project Module6/Module6.csprojLocation: Module7/Program.cs
Concept: Comprehensive overview of Task API methods and properties.
Key Features:
Task.FromResult()- Creating completed tasksTask.Run()- Running work on thread poolTask.Delay()- Non-blocking delaysTask.WhenAny()- Waiting for first completionTask.WaitAll()- Waiting for all tasksTask.ContinueWith()- Task continuation- Exception handling in tasks
What it demonstrates:
- Different ways to create and manage Tasks
- Task composition and coordination
- Synchronous vs asynchronous waiting
- Task exception propagation
Key Code:
// Completed task
Task<int> completedTask = Task.FromResult(42);
// Long-running task
Task longRunningTask = Task.Run(LongRunningOperation);
// Wait for first to complete
Task firstToFinish = await Task.WhenAny(completedTask, longRunningTask);
// Wait for all
Task.WaitAll([completedTask, longRunningTask]);
// Continuation
longRunningTask.ContinueWith(t => Console.WriteLine("Continued..."));
// Exception handling
try
{
await Task.Run(() => throw new Exception("Task exception!"));
}
catch (Exception ex)
{
Console.WriteLine($"Caught: {ex.Message}");
}Run:
dotnet run --project Module7/Module7.csprojLocation: Module8/Program.cs
Concept: Performance optimization using ValueTask<T> for potentially synchronous operations.
Key Features:
ValueTask<T>vsTask<T>comparison- Caching pattern implementation
- Avoiding Task allocation overhead
- Synchronous completion optimization
What it demonstrates:
- Using ValueTask for cache hits (synchronous path)
- Falling back to Task for cache misses (asynchronous path)
- Performance benefits of value types
- Dictionary-based caching pattern
Key Code:
private readonly Dictionary<int, User> _cache = new Dictionary<int, User>();
public ValueTask<User> GetUserAsync(int userId)
{
// Cache hit - return immediately without Task allocation
if (_cache.TryGetValue(userId, out User cachedUser))
{
return new ValueTask<User>(cachedUser);
}
else
{
// Cache miss - fetch asynchronously
return new ValueTask<User>(FetchUserFromDatabaseAsync(userId));
}
}
private async Task<User> FetchUserFromDatabaseAsync(int userId)
{
await Task.Delay(1000); // Simulate I/O
var user = new User { Id = userId, Name = "John Doe" };
_cache.Add(userId, user);
return user;
}Run:
dotnet run --project Module8/Module8.csprojLocation: Module9/Program.cs
Concept: Design patterns for asynchronous initialization and object creation.
Key Features:
- Async Factory Method Pattern - Creating objects asynchronously
- Async Singleton Pattern - Thread-safe singleton with async initialization
- Asynchronous Initialization Pattern - Complex object setup
What it demonstrates:
- Alternative to constructors for async initialization
- Thread-safe lazy initialization with Tasks
- Separation of object creation from initialization
- Real-world patterns for async object lifecycle
Key Code:
// Async Factory Method
public class ReportFactory
{
public static async Task<Report> CreateAsync(string title)
{
await Task.Delay(1000); // Simulate async setup
return new Report { Title = title };
}
}
// Async Singleton
public class Configuration
{
private static Task<Configuration> instanceTask;
public static Task<Configuration> GetInstanceAsync()
{
if (instanceTask == null)
{
instanceTask = InitializeAsync();
}
return instanceTask;
}
private static async Task<Configuration> InitializeAsync()
{
await Task.Delay(1000);
return new Configuration();
}
}
// Usage
var report = await ReportFactory.CreateAsync("Report Title");
var config = await Configuration.GetInstanceAsync();
var logger = await Logger.InitializeAsync();Run:
dotnet run --project Module9/Module9.csprojLocation: Module10/Program.cs
Concept: Best practices for async programming and deadlock prevention.
Key Principles Covered:
-
Avoid Async Void
- Use
async Taskinstead ofasync void(except for event handlers) - Different error-handling semantics prevent unobserved exceptions
- Use
-
Configure Context
- Use
ConfigureAwait(false)in library code - Prevents deadlocks by not capturing SynchronizationContext
- Use
-
Understand Synchronization Context
- How continuations are posted back to main thread
- Avoiding blocked contexts while awaiting
-
Use Task.Run for CPU-bound Work
- Keep main thread responsive
- Offload CPU-intensive work to thread pool
-
Handle Exceptions Properly
- Exceptions return as Faulted tasks
- Always await or inspect Task.Exception
-
Avoid Blocking Calls
- Never use
Task.ResultorTask.Wait() - Always use
awaitfor async waiting
- Never use
-
Monitor Deadlocks
- Follow "async all the way" principle
- Review code for blocking calls on async methods
Key Code:
// Good: Async Task method
public static async Task<string> ExceptionAsync()
{
await Task.Delay(5000);
return "Exceptions thrown in async method return as Faulted task";
}
// Good: Avoid blocking
public static async Task BlockAsync()
{
await Task.Delay(5000); // Instead of Task.Result or Task.Wait()
Console.WriteLine("Avoid blocking calls");
}
// Usage
await ExceptionAsync(); // Good: Using await
await BlockAsync(); // Good: Async all the wayRun:
dotnet run --project Module10/Module10.csproj- Thread creation and lifecycle
- Thread states and transitions
- Thread naming and identification
- ParameterizedThreadStart vs ThreadStart
- Thread joining and synchronization
- Lock: Mutual exclusion for critical sections
- Monitor: Advanced synchronization with Wait/Pulse
- Mutex: Cross-process synchronization
- Semaphore: Limiting concurrent access
- ReaderWriterLockSlim: Optimized read/write scenarios
- Manual thread creation vs ThreadPool
- ThreadPool configuration and optimization
- Work item queuing
- Thread pool sizing strategies
- async/await syntax and semantics
- Task and Task types
- ValueTask for performance optimization
- Async streams with IAsyncEnumerable
- Exception propagation in async methods
- Task-based Asynchronous Pattern (TAP)
- Async factory methods
- Async singleton pattern
- Async initialization pattern
- Delegate-based thread communication
- Deadlock prevention techniques
- ConfigureAwait usage
- Synchronization context understanding
- Proper exception handling
- "Async all the way" principle
- CPU-bound vs I/O-bound work separation
dotnet run --project MultiThreadPrac.csproj# Module 2 - Thread Joining and Cancellation
dotnet run --project Module2/Module2.csproj
# Module 5 - Async/Await
dotnet run --project Module5/Module5.csproj
# Module 10 - Best Practices
dotnet run --project Module10/Module10.csprojdotnet build MultiThreadPrac.sln- Open the solution in Visual Studio or VS Code
- Set the desired project as startup project
- Press F5 to run with debugging
- Use breakpoints to step through thread execution
- Always protect shared resources with appropriate synchronization
- Use the simplest synchronization mechanism that meets your needs
- Minimize the scope of locks to avoid contention
- Use async/await for I/O-bound operations
- Avoid async void except for event handlers
- Always await async methods ("async all the way")
- Use ConfigureAwait(false) in library code
- Properly dispose of synchronization primitives (CancellationTokenSource, Semaphore)
- Use thread pool for short-lived tasks
- Create manual threads only for long-running operations
- Wrap async operations in try-catch blocks
- Observe task exceptions to prevent silent failures
- Use CancellationToken for cooperative cancellation
- Use ValueTask when operations may complete synchronously
- Leverage ThreadPool instead of creating threads manually
- Minimize context switches and lock contention
- Separate synchronous and asynchronous code paths clearly
- Use meaningful names for threads for debugging
- Keep async method signatures consistent (Task or Task)
Contributions are welcome! If you'd like to add more examples or improve existing ones:
- Fork the repository
- Create a feature branch (
git checkout -b feature/new-module) - Add your module following the existing pattern:
- Create a new ModuleX folder
- Add a self-contained Program.cs
- Include comments explaining the concept
- Add a ModuleX.csproj file
- Update this README with module documentation
- Commit your changes (
git commit -am 'Add Module X: Description') - Push to the branch (
git push origin feature/new-module) - Create a Pull Request
Each module should follow this structure:
- Clear comments at the top explaining the concept
- Self-contained, runnable example
- Console output showing the behavior
- Demonstrates one primary concept
- Includes error handling where appropriate
This project is provided as-is for educational purposes. Feel free to use, modify, and distribute the code as you see fit.
- Threading in C# - Microsoft Docs
- Asynchronous Programming with async and await
- Task-based Asynchronous Pattern (TAP)
- "Concurrency in C# Cookbook" by Stephen Cleary
- "C# 10 in a Nutshell" by Joseph Albahari
- "CLR via C#" by Jeffrey Richter
Note: This is a practice repository created for learning purposes. The examples demonstrate various threading and async patterns in isolation. In production code, always consider the specific requirements and constraints of your application.
Framework: .NET 8.0
Language: C# 12
Author: Educational Project
Last Updated: 2025