Implementation of Parallel.ForEach for .NET 3.5 Because the TPL backport not only contains Parallel.* but also PLINQ, concurrent collections, etc, I will try to use this one.

Just for fun, I profiled both the TPL implementation and my own, in the very basic and naive test, mine was actually a little bit faster.

Microsoft TPL backport: 00:00:17.0673098
My Implementation: 00:00:16.9210535
Intel Q6600 (quad-core), 4GB of RAM, Win8.1(x64), VS2013 targetting .NET 3.5

The code is here (github gist), the colorful output of the Concurrency Visualizer is below:



This negligeable performance difference (in the best case) gets outweight by the many optimisations the .NET team put in for special cases.

Question:

To ease working with multithreaded code, I wanted to implement a version of Parallel.ForEach for .NET 3.5.

We are for the moment stuck with 3.5 and cannot upgrade.  We cannot use external libraries like TPL.

I found this via Google, but this threw sporadic exceptions during runtime, and because I couldn't find another Version I decided to roll my own.

I did of course test it, but i am always a bit cautious with multithreaded code.

public class Parallel
{
    public static void ForEach&lt;T&gt;(IEnumerable&lt;T&gt; items, Action&lt;T&gt; action)
    {
        if (items == null)
            throw new ArgumentNullException("enumerable");
        if (action == null)
            throw new ArgumentNullException("action");

        var resetEvents = new List&lt;ManualResetEvent&gt;();

        foreach (var item in items)
        {
            var evt = new ManualResetEvent(false);
            ThreadPool.QueueUserWorkItem((i) =&gt;
            {
                action((T)i);
                evt.Set();
            }, item);
            resetEvents.Add(evt);
        }

        foreach (var re in resetEvents)
            re.WaitOne();
    }
}
if (items == null)
        throw new ArgumentNullException("enumerable");



In the execution path where this exception is thrown, the reported argument name will not match the actual argument name. If this is the result of a rename refactoring (i.e. it's the parameter formerly known as "enumerable"), it's unfortunate, the string literal wasn't taken care of. That doesn't happen with ReShaper performing the refactoring, but if you're using the refactoring built into VS, I'm not sure it handles string literals; if you've manually "renamed" the argument, ...next time, use refactor/rename! ;)

Your implementation queues a work item for each item in items.


  ThreadPool.QueueUserWorkItem
  
  Queues a method for execution. The method executes when a thread pool thread becomes available.


The interesting bit is about its return value:


  Return Value
  
  Type: System.Boolean
  
  true if the method is successfully
  queued; NotSupportedException is thrown if the work item could not be
  queued.


There's the slight possibility that thread pooling might not be supported on the platform code is running on (from what I gathered of reasons for this exception to be thrown), in which case the first iteration will throw a NotSupportedException that the ForEach code might not be expecting at all... but letting it bubble up might just be the best thing to do in this case.



If I correctly understand what's going on, you're essentially spawning a thread to run an action, for each item in items. This might not be as efficient as you'd think it is: there's only so many concurrent operations that can happen on a quad-core processor, and then there's overhead too; sometimes it's more efficient to simply run the tasks sequentially.

And that's what Parallel.ForEach does:


  Parallel.ForEach
  
  Executes a foreach operation on an IEnumerable in which iterations may run in parallel.


Thus, I'd use your implementation with care, and perhaps use a profiler and benchmark against a "normal" foreach for a given number of iterations, but then your dev environment might have different computing power than your test and/or production environments, so careful with benchmarks, too.

It might be simpler to use the Microsoft NuGet package @JesseC.Slicer and @PhilBolduc mentioned in the comments...