Introduction: Why Do You Need Concurrency in C#?
Modern applications increasingly require performing multiple operations simultaneously. Users expect the interface not to "freeze" when loading data, and servers to handle thousands of requests in parallel. In C# (.NET), concurrency — the ability of a program to execute multiple tasks in overlapping time periods — and multithreading — the implementation of concurrency using multiple execution threads — are used to solve these problems.
In this article, we will break down key concepts: from basic Thread to modern async/await and Parallel. You will learn how to choose the right tool for each task and avoid common mistakes such as deadlocks and race conditions.
1. Basics: Thread and ThreadPool
Working with Threads via Thread
The System.Threading.Thread class is the lowest-level way to create threads. Each thread has its own stack and executes independently. Example of creating and starting a thread:
using System;using System.Threading;
class Program{ static void Main() { Thread thread = new Thread(DoWork); thread.Start(); // Main thread continues working for (int i = 0; i < 5; i++) { Console.WriteLine($"Main thread: {i}"); Thread.Sleep(100); } thread.Join(); // Wait for the child thread to finish }
static void DoWork() { for (int i = 0; i < 5; i++) { Console.WriteLine($"Worker thread: {i}"); Thread.Sleep(150); } }}Important: Creating a new thread is an expensive operation. For frequently creating short-lived threads, it is better to use ThreadPool.
ThreadPool — Thread Pool
ThreadPool manages a set of worker threads that are reused. This is more efficient than manually creating Thread. Example:
ThreadPool.QueueUserWorkItem(state =>{ Console.WriteLine("Task is executing in the thread pool"); Thread.Sleep(500); Console.WriteLine("Task completed");});
Console.WriteLine("Main thread continues working");Console.ReadKey();Limitation: You cannot directly control the priority or name of a thread from the pool. For finer control, use Task.
2. Task and Task Parallel Library (TPL)
Task — Modern Approach
Starting with .NET 4.0, Task became the primary tool for asynchronous operations. It abstracts threads and allows working with execution results. Example:
using System;using System.Threading.Tasks;
class Program{ static async Task Main() { Task<int> task = Task.Run(() => ComputeResult(10)); // Can do other work Console.WriteLine("Waiting for result..."); int result = await task; Console.WriteLine($"Result: {result}"); }
static int ComputeResult(int value) { Task.Delay(1000).Wait(); // Simulate a long operation return value * 2; }}Key advantages:
- Returns a result via
Task<T> - Supports cancellation via
CancellationToken - Allows combining tasks (ContinueWith, WhenAll, WhenAny)
- Integrated with
async/await
Parallel — For Parallel Loops
The Parallel class simplifies executing loops with parallelization. Example:
using System;using System.Threading.Tasks;
class Program{ static void Main()\