The tools .NET gives you for concurrency and parallelism

2 min read

Threads

Threads (System.Threading.Thread) are the most fundamental unit of execution that the operating system can manage. Managing them directly (creating, starting, stopping, synchronizing) is powerful but complex, and prone to errors like race conditions and deadlocks. Higher-level abstractions are generally preferred.

Task Parallel Library (TPL)

The TPL was introduced in .NET Framework 4, and it simplifies parallel and concurrent programming.

  • Task and Task<TResult> are the core types in the TPL, representing an asynchronous operation. They are used extensively with async/await for concurrency. They can also represent work to be done in parallel, for example with Task.Run or the Parallel class methods.
  • Parallel.For and Parallel.ForEach are a simple way to execute loop iterations in parallel, distributing the work across the available CPU cores. They suit data parallelism, where each iteration is largely independent.

In the example, the Parallel.ForEach call is on line 3 and the CPU-bound work in its body is on line 5. Results go into a ConcurrentBag, a thread-safe collection from the synchronization noteSynchronization primitives and concurrent collectionsWhat to use when several threads touch the same data.Opens in a new tab, on line 2.

C#
List<MyItem> itemsToProcess = GetItems();ConcurrentBag<ProcessedResult> results = new ConcurrentBag<ProcessedResult>();Parallel.ForEach(itemsToProcess, item =>{    ProcessedResult result = ProcessSingleItem(item); // CPU-bound work    results.Add(result);});

Two things to watch with this shape. A ConcurrentBag hands results back in the order the tasks finish, not the order of the input. If the order matters, use Parallel.For and write each result into an array slot by index. And MaxDegreeOfParallelism in ParallelOptions caps how many iterations run at once, usually at Environment.ProcessorCount, so the loop doesn’t oversubscribe the cores. A worked example is in fan-out and fan-in with Parallel.ForFan-out and fan-in with Parallel.ForRender independent items on all cores, keep the results in order, then merge them one at a time.Opens in a new tab, and how many iterations really run at once is in how many jobs Parallel.For can runHow many jobs can Parallel.For run?No limit on how many you submit, a real limit on how many run at once.Opens in a new tab.

  • PLINQ (Parallel LINQ) lets LINQ queries run in parallel. If you call the .AsParallel() extension method on an IEnumerable<T> source, the following LINQ operators can be processed concurrently across multiple threads. That can speed up CPU-bound query operations.

async and await for concurrency

As in the async chapterAsync and awaitAwait frees the thread while an operation waits, and the method picks up where it left off.Opens in a new tab, async and await are primarily designed for I/O-bound concurrency. They make efficient use of threads by releasing them during wait periods, rather than creating parallelism by default. async/await is about non-blocking operations, while the TPL and PLINQ are more directly about parallel execution on multiple cores.


Sources