Principles and Applications of Parallel.ForEach in C#: Converting from foreach to Parallel Loops

Nov 22, 2025 · Programming · 11 views · 7.8

Keywords: C# | Parallel.ForEach | Multithreading | Data Parallelism | Performance Optimization

Abstract: This article provides an in-depth exploration of how Parallel.ForEach works in C# and its differences from traditional foreach loops. Through detailed code examples and performance analysis, it explains when using Parallel.ForEach can improve program execution efficiency and best practices for CPU-intensive tasks. The article also discusses thread safety and data parallelism concepts, offering comprehensive technical guidance for developers.

Basic Concepts of Parallel.ForEach

In C# programming, Parallel.ForEach is a powerful tool introduced in .NET Framework 4.0 and later versions for implementing data parallelism. Unlike traditional foreach loops, Parallel.ForEach automatically distributes iteration tasks across multiple threads, leveraging the computational power of multi-core processors.

Core Differences Between foreach and Parallel.ForEach

Traditional foreach loops execute sequentially, with all iterations processed one by one on a single thread. This means if operations within the loop are time-consuming, the total execution time increases linearly. For example, when processing a collection with many elements, each element must wait for the previous one to complete.

In contrast, Parallel.ForEach uses the Task Parallel Library (TPL) to manage a thread pool automatically, partitioning the collection and processing these partitions concurrently on multiple threads. This mechanism is particularly suitable for CPU-intensive tasks, such as image processing or data computations, significantly reducing overall execution time.

Code Conversion Example

Based on the example from the Q&A data, we can convert a traditional foreach loop to Parallel.ForEach. The original code is:

string[] lines = File.ReadAllLines(txtProxyListPath.Text);
List<string> list_lines = new List<string>(lines);

foreach (string line in list_lines)
{
    //My Stuff
}

The rewritten code using Parallel.ForEach is:

string[] lines = File.ReadAllLines(txtProxyListPath.Text);
List<string> list_lines = new List<string>(lines);
Parallel.ForEach(list_lines, line =>
{
    //Your stuff
});

In this conversion, Parallel.ForEach takes two parameters: the collection to iterate over and a delegate (lambda expression) that defines the operation to perform on each element. The system automatically allocates threads to execute these operations in parallel.

Performance Analysis and Applicable Scenarios

From the example output in the Q&A data, Parallel.ForEach can significantly improve performance for time-consuming operations. In the color printing example, the sequential foreach loop took about 0.105 seconds, while Parallel.ForEach took only about 0.056 seconds, nearly doubling the speed.

However, Parallel.ForEach is not suitable for all scenarios. For very fast operations, the overhead of thread creation and management may outweigh the benefits of parallel execution, leading to performance degradation. Therefore, when deciding whether to use parallel loops, it is essential to evaluate the computational intensity and data dependencies of the operations.

Thread Safety and Data Sharing

When using Parallel.ForEach, thread safety must be considered. If multiple threads access shared resources simultaneously (e.g., static variables or external data sources), it may cause race conditions or data inconsistencies. The prime number calculation example in the reference article uses ConcurrentBag<T> to collect results safely, which is a thread-safe collection class.

For instance, in the prime filtering code:

private static IList<int> GetPrimeListWithParallel(IList<int> numbers)
{
    var primeNumbers = new ConcurrentBag<int>();
    Parallel.ForEach(numbers, number =>
    {
        if (IsPrime(number))
        {
            primeNumbers.Add(number);
        }
    });
    return primeNumbers.ToList();
}

Here, ConcurrentBag<int> is used to avoid thread conflicts when adding elements.

Practical Application Recommendations

In practical development, the following factors should be considered when using Parallel.ForEach:

By appropriately applying Parallel.ForEach, developers can significantly enhance application performance when handling large-scale data, especially on modern multi-core processors.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.