Concurrency and Multithreading in C#: From A to Z — Complete Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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()\

Blogs

Book Recommendations