Wednesday, February 18, 2009

C# Parallelism: Executing Methods in Parallel in .NET 3.5

To fully take advantage of multi-core processors and to speed up response times, developers have to embrace parallel design patterns.  Most of the code we write today is in serial; we execute one method after another in a linear fashion.  There are some pretty advanced libraries available to us thanks to the .NET Framework (e.g. ThreadPool, Thread), but for many reasons they are under utilized.

Coming in .NET 4.0 with Visual Studio 2010, we will get a whole new library that will allow us to easily take advantage of parallelism in our apps -- without drastically changing the code structure.  I think this is the biggest selling point here.  The .NET wizards at Microsoft took a look at the structure of our existing code and figured out some intuitive and non-intrusive ways for us to leverage parallel execution.  This article has a very thorough explanation of the new classes.

For now, we can use existing classes like ThreadPool, Thread, and BackgroundWorker to execute code in multiple threads, which in turn should be processed in parallel from the multiple cores available on the system.

I've written a simple helper method that will execute chunks of code in parallel using ThreadPool.  You can use the method in pretty much the same way as the Parallel.Do() method coming in .NET 4.0.  So once you're on 4.0, just swap out the method calls.

Example usage:

ParallelProcessor.ExecuteParallel(()=>
{
    // Get some data
}, ()=>
{
    // get some different data
}, ()=>
{
    // get some data from a web service
});

// do something else after all of the delegate methods have completed

Here is code for ExecuteParallel:

public class ParallelProcessor
{
    public delegate void Method();

    /// <summary>
    /// Executes a set of methods in parallel and returns the results
    /// from each in an array when all threads have completed.  The methods
    /// must take no parameters and have no return value.
    /// </summary>
    /// <param name="m"></param>
    /// <returns></returns>
    public static void ExecuteParallel(params Method[] methods)
    {
        // Initialize the reset events to keep track of completed threads
        ManualResetEvent[] resetEvents = new ManualResetEvent[methods.Length];

        // Launch each method in it's own thread
        for (int i = 0; i < methods.Length; i++)
        {
            resetEvents[i] = new ManualResetEvent(false);
            ThreadPool.QueueUserWorkItem(new WaitCallback((object index) =>
            {
                int methodIndex = (int)index;

                // Execute the method
                methods[methodIndex]();

                // Tell the calling thread that we're done
                resetEvents[methodIndex].Set();
            }), i);
        }

        // Wait for all threads to execute
        WaitHandle.WaitAll(resetEvents);
    }
}

9 comments:

  1. Not a bad idea, but your method doesn't do any exception handling. What it two or more methods both throw exceptions? What if you want to cancel the parallel methods? In any case, you should take a look at the Task Parallel Library/PLINQ/.NET 4 if you haven't already. Thanks for an interesting post though :-)

    ReplyDelete
  2. You can handle exceptions in the lambda methods that you pass in.

    This is supposed to be a simple way of doing tasks in parallel if you're running an older version of .NET 4. If you have .NET 4, go ahead and use the spankin' new parallel library! I couldn't wait so I wrote this up.

    ReplyDelete
  3. Congratz, simple and very usefull for guys without .NET 4 yet... :)

    ReplyDelete
  4. do it simpler, and more efficient:

    public static class Parallel {
    public static void Invoke(params Action[] actions) {
    var results = actions.Skip(1).Select(
    act => new { act, res = act.BeginInvoke(null, null) }).ToArray();
    actions[0].EndInvoke(actions[0].BeginInvoke(null, null)); /* let the mainthread do a job too*/
    foreach(var r in results) r.act.EndInvoke(r.res);
    }
    }

    ReplyDelete
  5. ups, the mainthread can work simpler:

    public static class Parallel {
    public static void Invoke(params Action[] actions) {
    var results = actions.Skip(1).Select(
    act => new { act, res = act.BeginInvoke(null, null) }).ToArray();
    actions[0](); /* let the mainthread do a job too*/
    foreach(var r in results) r.act.EndInvoke(r.res); /*then wait the rest of time */
    }
    }

    ReplyDelete
  6. How does this change with 4.0? Or even the upcoming release of 5.0?

    ReplyDelete
  7. This is helpful but I still don't understand it completely. How to pass arguments to the method? and more importantly, if arguments change with each iteration, then how does it handle that situation?

    ReplyDelete
  8. You don't pass arguments into the method. Instead you can reference variables declared in the calling method. Just make sure that if more than one thread accesses a variable, that you lock it using lock().

    ReplyDelete