Concurrency and Multithreading in C#: Advanced Level

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#: Advanced Level



In modern C# development, concurrency and multithreading are not just additional capabilities but fundamental tools for creating fast, responsive, and scalable applications. At an advanced level, we go beyond simply launching Task.Run and encounter the intricacies of schedulers, context synchronization, and patterns for working with shared data. In this article, we will break down key advanced concurrency concepts in C# (.NET) that will help you write efficient and safe code.



1. Task and ValueTask: In-depth Usage



The Task class is the foundation of asynchronous programming in C#. However, at an advanced level, it is important to understand the difference between Task and ValueTask, as well as manage the task lifecycle.



When to use ValueTask?

ValueTask is a structure that allows you to avoid allocating memory for a Task object when the result is already available synchronously. This is critical for high-performance scenarios, such as in caching methods.



public class CacheService{    private readonly Dictionary<string, string> _cache = new();

public async ValueTask<string> GetDataAsync(string key) { if (_cache.TryGetValue(key, out string value)) { // Result already exists, no Task created return value; }

// Asynchronous data retrieval string data = await FetchFromDatabaseAsync(key); _cache[key] = data; return data; }

private async Task<string> FetchFromDatabaseAsync(string key) { await Task.Delay(100); // Simulating delay return $"Data for {key}"; }}


Advanced Error Handling and Cancellation

Use CancellationToken for proper cancellation of long-running operations. Combine multiple tokens via CancellationTokenSource.CreateLinkedTokenSource.



public async Task ProcessWithTimeoutAsync(CancellationToken externalToken){    using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));    using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(externalToken, timeoutCts.Token);

try { await LongRunningOperationAsync(linkedCts.Token); } catch (OperationCanceledException) { Console.WriteLine("Operation cancelled or timeout exceeded."); }}


2. SynchronizationContext and Task Schedulers



Understanding SynchronizationContext is necessary for working with UI threads (WPF, WinForms) and ASP.NET Core. At an advanced level, you can create your own synchronization contexts.



Configuring Context for the Thread Pool

By default, await captures the current SynchronizationContext. To avoid this (and prevent deadlocks), use ConfigureAwait(false).



public async Task ProcessDataAsync(){    // This code runs in the original context (e.g., UI)    await Task.Run(() => HeavyComputation()).ConfigureAwait(false);

// After ConfigureAwait(false) — in the thread pool context // No access to UI elements! Console.WriteLine("Executed in a thread pool thread");}


Creating a Custom Scheduler

You can implement your own TaskScheduler, for example, to limit the number of concurrently executing tasks (similar to SemaphoreSlim at the scheduler level).



public class LimitedConcurrencyTaskScheduler : TaskScheduler{    private readonly SemaphoreSlim _semaphore;    private int _runningTasks;

public LimitedConcurrencyTaskScheduler(int maxConcurrency) { _semaphore = new SemaphoreSlim(maxConcurrency);

Blogs

Book Recommendations