Understanding C# Parallel.ForEach Loop

This post explains what C# Parallel.ForEach Loop is? how it works and when and how to use it effectively to enable data parallelism over any System.Collections.IEnumerable or System.Collections.Generic.IEnumerable<T> data source.

Understanding C# Parallel.ForEach

Getting Started

As application complexity and performance demands grow, developers increasingly rely on multi-threading to enhance execution speed, particularly for data processing tasks. In C#, the Parallel.ForEach loop offers a simple and efficient way to execute iterations concurrently, effectively utilizing multiple processor cores.

What is C# Parallel.ForEach?

Parallel.ForEach is part of the Task Parallel Library (TPL) in .NET, and It's a parallelized version of the regular foreach loop and is defined in the System.Threading.Tasks namespace which allows you to iterate over a collection with multiple threads, automatically managing the distribution of work across available cores.

Parallel.ForEach was introduced in .NET Framework 4.0, which was released in April 2010 to make multi-threaded and parallel programming more accessible and scalable on multi-core systems.

Key Details:
  • Namespace: System.Threading.Tasks
  • Library: Part of the Task Parallel Library (TPL)
  • Framework Version: .NET Framework 4.0
  • Also Available In:
    • .NET Core (from version 1.0 onward)
    • .NET 5/6/7/8+ (as part of the unified .NET platform)

Parallel.ForEach Performance

Parallel.ForEach can improve performance because the loop partitions the source collection and schedules the work on multiple threads based on the system environment means The more processors on the system, the faster the parallel method runs.

But it's not always faster than a regular foreach, for some source collections, a sequential loop might be faster. Its effectiveness depends on:
  • The size of the collection
  • The complexity of the operation
  • The number of processor cores
  • Thread contention or blocking

In C#, Parallel.ForEach can significantly improve performance by executing iterations of a loop concurrently. However, it's not always the right tool for the job. Here's a guide on when to use and when to avoid Parallel.ForEach.

When to Use

  1. CPU-bound Operations
    • Use it when you're doing intensive computations in each iteration.
    • Example: image processing, mathematical calculations, or parsing large data sets.
  2. Independent Iterations
    • Each iteration should be independent and not depend on results of others.
    • No shared state (or well-synchronized shared state).
  3. Large Datasets
    • Effective with a large collection of items (hundreds or thousands).
    • For small collections, parallelism overhead might outweigh the benefits.
  4. Multi-core Machines
    • Most beneficial on systems with multiple cores or processors.
    • It utilizes all available cores for concurrent execution.

When to Avoid

  1. I/O-bound Workloads
    • Avoid for disk, network, or database I/O (e.g., reading files, calling APIs).
    • Use async/await with foreach instead (await foreach or foreach with Task.WhenAll).
  2. Small Workload per Iteration If each iteration is fast/lightweight, the overhead of parallelism can slow things down.
  3. Order Matters: Parallel.ForEach does not guarantee order. Use a regular foreach if order is important.
  4. Shared State Without Synchronization: If your loop modifies shared variables without proper locking, you risk race conditions.
  5. UI Applications (e.g., WPF, WinForms)
    • Parallel.ForEach runs on background threads and can't update UI controls directly.
    • Use async patterns with proper marshaling to the UI thread.
  6. Controlling Parallelism is Important If you need throttling, cancellation, or progress reporting, Parallel.ForEach may be less flexible than Task-based approaches.

Basic Syntax

 Parallel.ForEach<TSource>(  
   IEnumerable<TSource> source,  
   Action<TSource> body  
 );  
  • TSource: The type of elements in the source collection.
  • source: The collection to iterate over.
  • body: The delegate that performs the operation on each element.

Example: Basic Usage

Here’s a basic example that processes a list of numbers in parallel.
using System;  
 using System.Collections.Generic;  
 using System.Threading.Tasks;  
 class Program  
 {  
   static void Main()  
   {  
     List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };  
     Parallel.ForEach(numbers, number =>  
     {  
       Console.WriteLine($"Processing {number} on thread {Task.CurrentId}");  
     });  
     Console.WriteLine("Finished processing.");  
   }  
 }  

Output: might look something like this (order may vary)
  Processing 1 on thread 1
  Processing 2 on thread 2
  Processing 3 on thread 3
  ...
  Finished processing.  
  

Example: Advanced Usage

Using ParallelOptions
You can configure behavior with ParallelOptions, such as limiting the number of concurrent tasks or handling cancellations.
  ParallelOptions options = new ParallelOptions  
 {  
   MaxDegreeOfParallelism = 4 // limit to 4 concurrent threads  
 };  
 Parallel.ForEach(numbers, options, number =>  
 {  
   Console.WriteLine($"Processing {number}");  
 });  

Cancellation Support
 CancellationTokenSource cts = new CancellationTokenSource();  
 ParallelOptions options = new ParallelOptions  
 {  
   CancellationToken = cts.Token  
 };  
 try  
 {  
   Parallel.ForEach(numbers, options, number =>  
   {  
     if (number == 3)  
       cts.Cancel(); // simulate cancellation  
     Console.WriteLine($"Processing {number}");  
   });  
 }  
 catch (OperationCanceledException)  
 {  
   Console.WriteLine("Operation was canceled.");  
 }  

Thread-Safety
If you're writing to shared variables inside the loop, use thread-safe techniques such as lock, ConcurrentBag, Interlocked, or PLINQ features.
 int total = 0;  
 object lockObj = new object();  
 Parallel.ForEach(numbers, number =>  
 {  
   lock (lockObj)  
   {  
     total += number;  
   }  
 });  

With Non-Generic Collections

The Parallel.ForEach loop was introduced to work with generic collections, but it can also be used with non-generic collections by using the Enumerable.Cast extension method to convert the collection to a generic type, as shown in the following example.

Parallel.ForEach(nonGenericCollection.Cast<object>(),  
   currentElement =>  
   {  
   });  

Summary

Parallel.ForEach is a powerful tool in C# for accelerating operations over large collections by distributing work across multiple threads. While it's easy to use, it should be applied thoughtfully, with consideration of thread-safety and performance overhead. In the right scenarios, it can significantly reduce processing time and help you make the most of modern multi-core processors.

Thanks

Kailash Chandra Behera

An IT professional with over 13 years of experience in the full software development life cycle for Windows, services, and web-based applications using Microsoft .NET technologies. Demonstrated expertise in delivering all phases of project development—from initiation to closure—while aligning with business objectives to drive process improvements, competitive advantage, and measurable bottom-line gains. Proven ability to work independently and manage multiple projects successfully. Committed to the efficient and effective development of projects in fast-paced, deadline-driven environments. Skills: Proficient in designing and developing applications using various Microsoft technologies. Total IT Experience: 13+ years

Previous Post Next Post

نموذج الاتصال