Concurrency and Multithreading in C#: A 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

Concurrency and Multithreading in C#: A Complete Guide

Modern applications require high performance and responsiveness. Concurrency and multithreading in C# are key tools that allow you to execute multiple tasks simultaneously, efficiently use multi-core processors, and improve user experience. In this article, we will break down the core concepts, starting from classic threads and ending with modern async/await and Parallel LINQ.



Basics of Multithreading: The Thread Class and Thread Pool

Multithreading in C# begins with the System.Threading.Thread class. Each thread represents a separate path of execution within a process. However, manually creating threads is an expensive operation. For optimization, the thread pool (ThreadPool) is used, which manages the reuse of threads.

Example of manually creating a thread:

using System;using System.Threading;

class Program{ static void Main() { Thread thread = new Thread(DoWork); thread.Start(); Console.WriteLine("Main thread is working..."); thread.Join(); // Wait for completion }

static void DoWork() { Console.WriteLine("Thread is performing work."); }}

For simpler usage, ThreadPool is recommended:

ThreadPool.QueueUserWorkItem(state => {    Console.WriteLine("Task in the thread pool.");});

Important: The Thread class has a low-level API and does not support returning a result. For modern applications, use Task.



Task and TPL (Task Parallel Library)

The TPL library (System.Threading.Tasks) provides a high-level abstraction — Task. A Task represents an asynchronous operation that can return a value. This is the foundation for modern concurrency in C#.

Example of a Task without a return value:

using System;using System.Threading.Tasks;

class Program{ static async Task Main() { Task task = Task.Run(() => DoWork()); await task; Console.WriteLine("Completed."); }

static void DoWork() { Console.WriteLine("Work in Task."); }}

Task with a return value:

Task<int> task = Task.Run(() => {    int result = 0;    for (int i = 0; i < 1000000; i++) result += i;    return result;});int sum = await task;Console.WriteLine($"Sum: {sum}");

TPL also includes Parallel.For and Parallel.ForEach for parallel processing of collections:

Parallel.For(0, 10, i => {    Console.WriteLine($"Iteration {i} on thread {Thread.CurrentThread.ManagedThreadId}");});


Async/await: Asynchronous Programming

The async and await keywords simplify writing asynchronous code, making it look like synchronous code. They do not create new threads, but efficiently switch the execution context, freeing the thread for other tasks.

Example of an asynchronous method:

using System;using System.Net.Http;using System.Threading.Tasks;

class Program{ static async Task Main() { string data = await FetchDataAsync("https://example.com"); Console.WriteLine(data); }

static async Task<string> FetchDataAsync(string url) { using HttpClient client = new HttpClient(); string result = await client.GetStringAsync(url); return result; }}

Usage rules:

  • A method with async must return Task, Task<T>, or void (only for event handlers

Blogs

Book Recommendations