Tuesday, 5 February 2013

Exception Handling with Parallel.Foreach

The .NET Framework's Parallel.Foreach is genius.  It eases the pain of making multiple tasks run in parallel.  It's just the ticket!  Oh, if only it were that simple.

What order do the tasks run in?  What when the parallel tasks throw exceptions?  How should that be handled?  When one task fails should the others be cancelled? Do we need to roll anything back?  What state updates are needed?  It soon becomes quite complex, quite quickly.

The project I'm working on right now has the following requirements:
  • It does not matter which order the tasks execute in;
  • There is no requirement to roll back the transaction should any individual task fail
  • If an exception is thrown, a SINGLE exception should be propagated upwards
The following achieves this, demonstrated in a console app, written in C#.


using System;
using System.Threading.Tasks;

namespace DavidBond.Net.ParallelTest
{
    /// <summary>
    /// Command line demonstration of Parallel.Foreach when Exceptions are thrown
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Program entry point
        /// </summary>
        public static void Main()
        {
            Console.WriteLine("Program start.");

            // Create an array of task names
            var taskList = new[]
                {
                    "Task A",
                    "Task B throws Exception",
                    "Task C",
                    "Task D throws Exception"
                };

            // Try to run all the tasks in parallel
            try
            {
                // The following line is the equivalent of:
                //Parallel.ForEach(taskList, taskName => Execute(taskName));
                Parallel.ForEach(taskList, Execute);
            }
            // If there are any exceptions, wait until all tasks have completed
            catch (AggregateException aggregateException)
            {
                foreach (var innerException in aggregateException.InnerExceptions)
                {
                    Console.WriteLine("Exception occurred on task.  Exception message was [{0}]", innerException.Message);
                }
                // Uncomment the next line to escalate multiple underlying exceptions as a single exception.
                // throw new Exception("Not all tasks completed successfully.");
            }

            Console.WriteLine("Program complete.");
        }

        /// <summary>
        /// Writes the line "[taskName] ran successfully" to the console
        /// </summary>
        /// <exception cref="Exception">Thrown if the taskname contains the word "exception" (not case sensitive).</exception>
        /// <param name="taskName">The task name</param>
        private static void Execute(string taskName)
        {
            //
            if (taskName.ToLower().Contains("exception"))
            {
                throw new Exception(string.Format("Exception thrown by task [{0}]", taskName));
            }

            Console.WriteLine("{0} ran successfully", taskName);
        }
    }
}


DO NOT copy-and-paste this example if your situation varies considerably.
Props to http://www.manoli.net/csharpformat/ for the formatting!

1 comment: