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.