Parallel.ForEach vs traditional foreach in C#

In this post I am going to talk about Parallel.ForEach vs traditional foreach in C#. I will give some examples when and why to use each one of them.

What is Parallel.ForEach?

Parallel.ForEach was introduced in .NET Framework 4.0, an alternative to the traditional foreach method in C#. Parallel.ForEach differs from its namesake in that it executes the iterations in parallel, by starting a new thread every time.

The following example shows a simple usage of the method:
C#
var cities = new List<string>();
cities.Add("London");
cities.Add("New York");
cities.Add("Tokyo");
cities.Add("Sidney");
cities.Add("Cairo");
cities.Add("Lima");

Parallel.ForEach(cities, (s) =>
{
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
});

This example will log the current thread ID for each interaction. The result for this code snipped is:

Parallel.ForEach - simple usage
Parallel.ForEach vs traditional foreach in C# – simple usage of Parallel.ForEach

As you can see for each iteration, there is a new thread started. Of course, you can control the number of threads to be started by passing a ParallelOptions parameter:

C#
var cities = new List<string>();
cities.Add("London");
cities.Add("New York");
cities.Add("Tokyo");
cities.Add("Sidney");
cities.Add("Cairo");
cities.Add("Lima");

Parallel.ForEach(cities, new ParallelOptions() { MaxDegreeOfParallelism = 2 }, (s) =>
{
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
});

The result from the code above will be:

Parallel.ForEach - simple usage with thread limit
Parallel.ForEach vs traditional foreach in C# – simple usage of Parallel.ForEach with thread limit

As you can see only two threads were started here – with ID 1 and with ID 6. They were limited by the MaxDegreeOfParallelism property.

Parallel.ForEach vs traditional foreach – example with fastest foreach

Let’s see the following example:

C#
private const int FakeJobTimeInMs = 1;

static void Main()
{
    var cities = new List<string>();
    cities.Add("London");
    cities.Add("New York");
    cities.Add("Tokyo");
    cities.Add("Sidney");
    cities.Add("Cairo");
    cities.Add("Lima");

    // Execute Parallel.ForEach
    var stopwatch = Stopwatch.StartNew();
    Parallel.ForEach(cities, (s) =>
    {
        Console.WriteLine(s);
    });
    Console.WriteLine($"Parallel foreach time elapsed: {stopwatch.Elapsed.TotalSeconds}");
    stopwatch.Stop();

    // Execute traditional foreach
    stopwatch = Stopwatch.StartNew();
    foreach (var city in cities)
    {
        Console.WriteLine(city);
    }
    Console.WriteLine($"Traditional foreach time elapsed: {stopwatch.Elapsed.TotalSeconds}");
    stopwatch.Stop();
}

In this example, we have a collection of cities, which are iterated twice with Parallel.Foreach and with the traditional foreach method. Each city is just logged in the console. There is also a Stopwatch defined for both iterations, to determine how many seconds each of them costs to complete.

The result from this code will be:

Parallel.ForEach vs traditional foreach - example with fastest foreach
Parallel.ForEach vs traditional foreach in C# – example with fastest foreach

We can see that the traditional foreach method is much faster in this example. But let’s see when the Paralel.Foreach will be more useful in the next section.

Parallel.ForEach vs traditional foreach – example with fastest Parallel.ForEach

Let’s extend the example and add some heavy functionality (not just console logging) in the iterations. We simulate some work in the loops by calling the DoSomeJob method, which is making Thread.Sleep. Here we pass the 100ms to the Thread.Sleep method, to simulate some delay each time a city is iterated:

C#
private const int FakeJobTimeInMs = 100;

static void Main()
{
    var cities = new List<string>();
    cities.Add("London");
    cities.Add("New York");
    cities.Add("Tokyo");
    cities.Add("Sidney");
    cities.Add("Cairo");
    cities.Add("Lima");

    // Execute Parallel.ForEach
    var stopwatch = Stopwatch.StartNew();
    Parallel.ForEach(cities, (s) =>
    {
        DoSomeJob(FakeJobTimeInMs);
    });
    Console.WriteLine($"Parallel foreach time elapsed: {stopwatch.Elapsed.TotalSeconds}");
    stopwatch.Stop();

    // Execute traditional foreach
    stopwatch = Stopwatch.StartNew();
    foreach (var city in cities)
    {
        DoSomeJob(FakeJobTimeInMs);
    }
    Console.WriteLine($"Traditional foreach time elapsed: {stopwatch.Elapsed.TotalSeconds}");
    stopwatch.Stop();
}

private static void DoSomeJob(int timeInMs)
{
    Thread.Sleep(timeInMs);
}

The result from this code is as follows:

Parallel.ForEach vs traditional foreach - example with fastest Parallel.ForEach
Parallel.ForEach vs traditional foreach in C# – example with fastest Parallel.ForEach

So, we can see that the Parallel.ForEach is faster than the traditional foreach here.

Conclusion

The Parallel.ForEach is executing our logic in multiple threads, and this sounds great the first time. You may think that you can replace all of your foreach loops with the parallel version because there is no way it won’t be faster when working on multiple threads simultaneously. But that’s not the case because multithreading has its price. As we can see from the examples above the foreach performs better on fast operations. That’s because C# needs some time to initialize a new thread at each iteration in the Parallel.ForEach method. And for fast operations, it is not worth investing this time, rather than just using the traditional foreach.

So, in conclusion:

  1. Use Parallel.ForEach on large collections, with heavy operations. In this case, the time for instantiating each thread will be nothing compared, to the speed you will achieve.
  2. Use foreach for iterations, which don’t require heavy operations. This can be faster in many cases, compared to the Parallel.ForEach. You will not invest time in starting a new thread for each item of the collection.

Resources