How to use Parallel.For and Parallel.ForEach in C#
By FoxLearn 1/6/2025 4:28:48 AM 244
.NET introduced support for parallel programming in .NET Framework 4.
Concurrency and parallelism in .NET Core
Concurrency and parallelism are key concepts in .NET and .NET Core, with subtle differences between them. In concurrent execution, tasks (T1 and T2) take turns running, with one task waiting while the other executes. In parallel execution, both tasks run simultaneously. Achieving parallelism requires a multi-core CPU.
Parallel.For and Parallel.ForEach in .NET Core
The Parallel.For
loop allows iterations to run in parallel across multiple threads, similar to a regular for
loop but with parallel execution. The Parallel.ForEach
method divides the work into separate tasks for each item in a collection. Unlike the sequential foreach
loop, Parallel.ForEach
runs tasks in parallel across multiple threads.
Parallel.ForEach vs foreach in C#
Consider the following example, where we check whether a number is a palindrome by using a method that accepts a string as a parameter and returns true if it is a palindrome.
static bool IsPalindrome(string str) { int start = 0; int end = str.Length - 1; while (start < end) { if (str[start] != str[end]) return false; start++; end--; } return true; }
We will now use ConcurrentDictionary
to store the palindromes and the thread IDs that processed them. Since each palindrome string is unique, we can store the string as a key and the managed thread ID as the value. The ConcurrentDictionary
class in .NET, found in the System.Collections.Concurrent
namespace, provides thread-safe and lock-free implementations for dictionary-like collections.
The following two methods both use the IsPalindrome
method to check if a string is a palindrome, store the palindromes and managed thread IDs in a ConcurrentDictionary
, and return the dictionary. The first method uses concurrency, and the second method uses parallelism.
private static ConcurrentDictionary<string, int> GetPalindromesConcurrent(IList<string> words) { var palindromes = new ConcurrentDictionary<string, int>(); foreach (var word in words) { if (IsPalindrome(word)) { palindromes.TryAdd(word, Thread.CurrentThread.ManagedThreadId); } } return palindromes; } private static ConcurrentDictionary<string, int> GetPalindromesParallel(IList<string> words) { var palindromes = new ConcurrentDictionary<string, int>(); Parallel.ForEach(words, word => { if (IsPalindrome(word)) { palindromes.TryAdd(word, Thread.CurrentThread.ManagedThreadId); } }); return palindromes; }
In this example, the GetPalindromesConcurrent
method iterates over the list of words sequentially, checking each one for being a palindrome and storing the result in the ConcurrentDictionary
. In contrast, the GetPalindromesParallel
method processes the words concurrently, using Parallel.ForEach
, which allows multiple threads to work simultaneously.
Concurrent vs parallel in C#
The following code snippet shows how you can invoke the GetEvenNumbersConcurrent
method to retrieve all even numbers between 1 and 100, along with the managed thread IDs.
static void Main(string[] args) { var numbers = Enumerable.Range(1, 100).ToList(); var result = GetEvenNumbersConcurrent(numbers); foreach (var number in result) { Console.WriteLine($"Even Number: {string.Format("{0:0000}", number.Key)}, Managed Thread Id: {number.Value}"); } Console.Read(); } private static ConcurrentDictionary<int, int> GetEvenNumbersConcurrent(IList<int> numbers) { var evens = new ConcurrentDictionary<int, int>(); foreach (var number in numbers) { if (number % 2 == 0) { evens.TryAdd(number, Thread.CurrentThread.ManagedThreadId); } } return evens; }
When executed, the output will show that the managed thread ID remains the same for each even number, as concurrency was used (one thread processes the numbers).
For example, here's how you can retrieve even numbers between 1 and 100 using parallelism:
static void Main(string[] args) { var numbers = Enumerable.Range(1, 100).ToList(); var result = GetEvenNumbersParallel(numbers); foreach (var number in result) { Console.WriteLine($"Even Number: {string.Format("{0:0000}", number.Key)}, Managed Thread Id: {number.Value}"); } Console.Read(); } private static ConcurrentDictionary<int, int> GetEvenNumbersParallel(IList<int> numbers) { var evens = new ConcurrentDictionary<int, int>(); Parallel.ForEach(numbers, number => { if (number % 2 == 0) { evens.TryAdd(number, Thread.CurrentThread.ManagedThreadId); } }); return evens; }
When executed, the output will show that different thread IDs are used for different even numbers, because the program uses Parallel.ForEach
, allowing multiple threads to process the numbers concurrently.
Limit the degree of parallelism in C#
The degree of parallelism is a key concept that defines the maximum number of threads that can run concurrently when processing tasks.
By default, methods like Parallel.For
or Parallel.ForEach
don’t limit the number of tasks they spawn. However, we can control this behavior by setting the MaxDegreeOfParallelism
property.
Let’s look at an example where we want to limit the degree of parallelism to 50% of the system’s available processors.
private static ConcurrentDictionary<int, int> GetEvenNumbersWithLimitedParallelism(IList<int> numbers) { var evens = new ConcurrentDictionary<int, int>(); // Define ParallelOptions to limit the degree of parallelism var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 0.5) * 2.0)) // 50% of available CPU resources }; Parallel.ForEach(numbers, parallelOptions, number => { if (number % 2 == 0) { evens.TryAdd(number, Thread.CurrentThread.ManagedThreadId); } }); return evens; }
In this example:
- The
ParallelOptions
object is used to configure the parallel execution of theParallel.ForEach
loop. - We set
MaxDegreeOfParallelism
to 50% of the system’s processor resources by calculating the number of processors (viaEnvironment.ProcessorCount
), multiplying it by 0.5, and assuming each processor has two cores (hence multiplying by 2). - This limits the number of threads that can run in parallel, preventing the system from becoming overburdened.
Determine if a parallel loop is complete in C#
In C#, both Parallel.For
and Parallel.ForEach
methods return a ParallelLoopResult
object, which can be used to check whether the parallel loop has completed execution.
Let’s go through an example where we use Parallel.ForEach
to process a collection of numbers, checking if each number is divisible by 3. Once the loop is complete, we can check the IsCompleted
property of the ParallelLoopResult
to determine if the parallel execution has finished.
private static ConcurrentDictionary<int, int> GetDivisibleByThreeNumbers(IList<int> numbers) { var divisibleByThree = new ConcurrentDictionary<int, int>(); // Initialize ParallelOptions to set the maximum degree of parallelism if needed var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling(Environment.ProcessorCount * 0.5)) // Limit to 50% of CPU resources }; // Execute Parallel.ForEach to process the numbers in parallel ParallelLoopResult parallelLoopResult = Parallel.ForEach(numbers, parallelOptions, number => { if (number % 3 == 0) { divisibleByThree.TryAdd(number, Thread.CurrentThread.ManagedThreadId); } }); // Check if the parallel loop has completed Console.WriteLine("IsParallelLoopCompleted: {0}", parallelLoopResult.IsCompleted); return divisibleByThree; }
In this example:
- We use the
Parallel.ForEach
method to iterate over thenumbers
list, checking if each number is divisible by 3. - The
ParallelLoopResult
object (parallelLoopResult
) contains anIsCompleted
property, which will betrue
if the parallel loop executed successfully without interruption. - After the loop completes, we print the status of
IsCompleted
to indicate whether all iterations finished without being cancelled or encountering errors.
- How to fix 'Failure sending mail' in C#
- How to Parse a Comma-Separated String from App.config in C#
- How to convert a dictionary to a list in C#
- How to retrieve the Executable Path in C#
- How to validate an IP address in C#
- How to retrieve the Downloads Directory Path in C#
- C# Tutorial
- Dictionary with multiple values per key in C#