tags:

views:

2131

answers:

5

In an application I work on, any business logic error causes an exception to be thrown, and the calling code handles the exception. This pattern is used throughout the application and works well.

I have a situation where I will be attempting to execute a number of business tasks from inside the business layer. The requirement for this is that a failure of one task should not cause the process to terminate. Other tasks should still be able to execute. In other words, this is not an atomic operation. The problem I have is that at the end of the operation, I wish to notify the calling code that an exception or exceptions did occur by throwing an exception. Consider the following psuedo-code snippet:

function DoTasks(MyTask[] taskList)
{
  foreach(MyTask task in taskList)
  {
    try
    {
       DoTask(task);
    }
    catch(Exception ex)
    {
        log.add(ex);
    }
  }

  //I want to throw something here if any exception occurred
}

What do I throw? I have encountered this pattern before in my career. In the past I have kept a list of all exceptions, then thrown an exception that contains all the caught exceptions. This doesn't seem like the most elegant approach. Its important to preserve as many details as possible from each exception to present to the calling code.

Thoughts?


Edit: The solution must be written in .Net 3.5. I cannot use any beta libraries, or the AggregateException in .Net 4.0 as mentioned by Bradley Grainger (below) would be a nice solution for collection exceptions to throw.

A: 

No super-elegant solution here but a few ideas:

  • Pass an error-handler function as argument to DoTasks so the user can decide whether to continue
  • Use tracing to log errors as they occur
  • Concatenate the messages from the other exceptions in the exception bundle's message
Cristian Libardo
+2  A: 

You might want to use a BackgroundWorker to do this for you. It automatically captures and presents any exceptions when completed, which you could then throw or log or do whatever with. Also, you get the benefit of multithreading.

The BackgroundWorker is a nice wrapper around delegate's asynchronous programming model.

Will
That is a good idea, but the problem of aggregation and rethrow still exists. Also, my psuedo-code is a simplification of an actual multi-threaded scenario where multiple exceptions can be retrieved from the background threads. I just didn't want to confuse the issue with that info.
Jason Jackson
+4  A: 

You could create a custom Exception that itself has a collection of Exceptions. Then, in your Catch block, just add it to that collection. At the end of your process, check if the Exception count is > 0, then throw your custom Exception.

Gene
That's the answer given in the question itself.
asterite
+7  A: 

Two ways of the top of my head would be either make a custom exception and add the exceptions to this class and throw that the end :

public class TaskExceptionList : Exception
{
    public List<Exception> TaskExceptions { get; set; }
    public TaskExceptionList()
    {
        TaskExceptions = new List<Exception>();
    }
}

    public void DoTasks(MyTask[] taskList)
    {
        TaskExceptionList log = new TaskExceptionList();
        foreach (MyTask task in taskList)
        {
            try
            {
                DoTask(task);
            }
            catch (Exception ex)
            {
                log.Add(ex);
            }
        }

        if (log.TaskExceptions.Count > 0)
        {
            throw log;
        }
    }

or return true or false if the tasks failed and have a 'out List' variable.

    public bool TryDoTasks(MyTask[] taskList, out List<Exception> exceptions)
    {
        exceptions = new List<Exception>();
        foreach (MyTask task in taskList)
        {
            try
            {
                DoTask(task);
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }
        }

        if (exceptions.Count > 0)
        {
            return false;
        }
        else
        {
            exceptions = null;
            return true;
        }
    }
Hath
Your second solution feels right to me.
Joshua Tacoma
I like the second solution, but the project I am working with has the "throw an exception for a business rule violation" built in everywhere. This would really violate that pattern.
Jason Jackson
+7  A: 

The Task Parallel Library extensions for .NET (which will become part of .NET 4.0) follow the pattern suggested in other answers: collecting all exceptions that have been thrown into an AggregateException class.

By always throwing the same type (whether there is one exception from the child work, or many), the calling code that handles the exception is easier to write.

In the .NET 4.0 CTP, AggregateException has a public constructor (that takes IEnumerable<Exception>); it may be a good choice for your application.

If you're targeting .NET 3.5, consider cloning the parts of the System.Threading.AggregateException class that you need in your own code, e.g., some of the constructors and the InnerExceptions property. (You can place your clone in the System.Threading namespace inside your assembly, which could cause confusion if you exposed it publicly, but will make upgrading to 4.0 easier later on.) When .NET 4.0 is released, you should be able to “upgrade” to the Framework type by deleting the source file containing your clone from your project, changing the project to target the new framework version, and rebuilding. Of course, if you do this, you need to carefully track changes to this class as Microsoft releases new CTPs, so that your code doesn't become incompatible. (For example, this seems like a useful general-purpose class, and they could move it from System.Threading to System.) In the worst case, you can just rename the type and move it back into your own namespace (this is very easy with most refactoring tools).

Bradley Grainger
How I wish we already had this library. I have been messing with it a little on the side, and it is awesome. Unfortunately, we are targeting .Net 3.5.
Jason Jackson
Will
I wouldn't put it into production. That is a CTP.
Jason Jackson
I am writing a class similar to AggreateException, and might replace it when .Net 4.0 comes out.
Jason Jackson