I was looped into a question from a customer today regarding how to best handle exceptions generated by subordinate threads from within the owning thread. They were using threads as a mechanism to do a set of primary tasks in parallel. By primary I mean these aren't just actions that disappear into the background to be executed "at some time in the future," but rather had normal scheduler priority and were responsible for the main workload of the application.
There are plenty of situations where this is a good practice, for example when you need to continually refresh the UI to notify the user of progress. Moreover, if calculations are highly independent and one or more might block and/or if you're on a multi-processor machine, it's often useful to split tasks into many concurrent mini-programs, kick them off, and simply have the parent join on them.
Anyhow, the customer's primary question was around the best way to propagate exceptions back to the owning thread. Truthfully, there isn't a single great way to do this, and it highly depends on your scenario. I did, however, give a simple code snippet of one thought on how to abstract the process of doing this. I figured somebody out there might also find it useful.
The basic idea is to create a thread worker class which encapsulates thread execution and handling of any subsequent errors and calculated values. E.g.
public class ThreadWorker<T>
{
// fields
private T workerReturnValue;
private Exception workerException;
private Thread workerThread;
private ThreadWorkerStart<T> workerStart;
// ctors
public ThreadWorker(ThreadWorkerStart<T> start)
{
workerStart = start;
}
// properties
public T WorkerReturnValue
{
get { return workerReturnValue; }
}
public Exception WorkerException
{
get { return workerException; }
}
// methods
public void Start()
{
workerThread = new Thread(Worker);
workerThread.Start();
}
public T Join()
{
workerThread.Join();
if (workerException != null)
throw new Exception("Worker threw exception", workerException);
return workerReturnValue;
}
private void Worker()
{
try
{
workerReturnValue = workerStart();
}
catch (Exception e)
{
workerException = e;
}
}
}
The Join() method here will either re-throw the exception generated in the ThreadWorkerStart method, or return the calculated value if no exception was generated. This enables you to handle it in the parent thread. Admittedly, the only case you want to do this is when the parent thread can take some corrective action and/or re-run the thread entirely. Most of the time, you want to try and handle exceptions as locally as possible, i.e. in the worker start method itself. However, if the worker thread is unable to do so, letting it leak is sometimes the right thing to do.
For example, this snippetdemonstrates both scenarios:
Random r = new Random();
for (int i = 0; i < 15; i++)
{
ThreadWorker<int> t = new ThreadWorker<int>(delegate()
{
int value = r.Next();
if ((value % 3) == 0)
throw new Exception("Uh oh, something bad happened");
else
return value;
});
t.Start();
try
{
Console.WriteLine("{0}. Worker output: {1}", i, t.Join());
}
catch (Exception ex)
{
Console.WriteLine("{0}. Worker exception: {1}", i, ex.InnerException.ToString());
}
}
Here, we generate, execute, and join on 15 threads, each of which will throw an exception should a random number be divisible by 3. Our code will print out either the computed value or the exception depending on whether the worker method throws an exception or not.