Archive

Archive for May, 2010

Handle all uncaught exceptions thrown when using Task Parallel Library (take 2)

May 7th, 2010 3 comments

A couple of days ago I posted a solution to handle all uncaught exceptions when using TPL. I’ve found a better way to do that, making use of task continuation. The class ThreadFactory below exposes the Error event which can be subscribed by a top-level handler and provides methods to start a task attached with proper continuation.

internal class ThreadFactory
{
    public delegate void TaskError(Task task, Exception error);

    public static readonly ThreadFactory Instance = new ThreadFactory();

    private ThreadFactory() {}

    public event TaskError Error;

    public void InvokeError(Task task, Exception error)
    {
        TaskError handler = Error;
        if (handler != null) handler(task, error);
    }

    public void Start(Action action)
    {
        var task = new Task(action);
        Start(task);
    }

    public void Start(Action action, TaskCreationOptions options)
    {
        var task = new Task(action, options);
        Start(task);
    }

    private void Start(Task task)
    {
        task.ContinueWith(t => InvokeError(t, t.Exception.InnerException),
                            TaskContinuationOptions.OnlyOnFaulted |
                            TaskContinuationOptions.ExecuteSynchronously);
        task.Start();
    }
}
Categories: .NET Tags: , , ,

Handle all uncaught exceptions thrown when using Task Parallel Library

May 4th, 2010 2 comments

I’m using the TPL (Task Parallel Library) in .NET 4.0. I want to be able to centralize the handling logic of all unhandled exceptions by using the Thread.GetDomain().UnhandledException event. However, in my application, the event is never fired for threads started with TPL code, e.g. Task.Factory.StartNew(...). The event is indeed fired if I use something like new Thread(threadStart).Start().

This MSDN article suggests to use Task#Wait() to catch the AggregateException when working with TPL, but that is not I want because it is not “centralized” enough a mechanism.

I’ve posted the above issue (verbatim) in this StackOverflow question but didn’t receive any response. So, I had to roll out some custom code to do that. Below is the description of the solution for those who are interested.

The general idea is pretty simple. First, we need to know all the currently executed tasks. Since there’s no built-in way to know which tasks are being executed, we need to intercept the task addition. The class TaskFactoryWrapper below does just that.

static class TaskFactoryWrapper
{
    public static void Start(Action action)
    {
        var task = new Task(action);
        TaskErrorWatcher.Instance.AddTask(task);
        task.Start();
    }

    public static void Start(Action action, TaskCreationOptions options)
    {
        var task = new Task(action, options);
        TaskErrorWatcher.Instance.AddTask(task);
        task.Start();
    }
}

Inside the class TaskErrorWatcher, there’s a worker thread which periodically checks each task to see if the latter already finishes, is canceled or causes an error. If it causes an error, the worker thread trigger the error event.

class TaskErrorWatcher
{
    public delegate void TaskError(Task task, Exception error);
    private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
    private static readonly TaskErrorWatcher @Instance = new TaskErrorWatcher();

    public static TaskErrorWatcher Instance
    {
        get
        {
            return @Instance;
        }
    }

    private TaskErrorWatcher()
    {
        Task.Factory.StartNew(Monitor);
    }

    public event TaskError Error;

    public void InvokeError(Task task, Exception error)
    {
        TaskError handler = Error;
        if (handler != null) handler(task, error);
    }

    private readonly List<Task> tasks = new List<Task>();
    private readonly object syncLock = new object();

    public void AddTask(Task task)
    {
        lock (syncLock)
        {
            tasks.Add(task);
        }
    }

    private void Monitor()
    {
        Utils.Interval(3000, () =>
                                {
                                    lock (syncLock)
                                    {
                                        for (int i = tasks.Count - 1; i >= 0; i--)
                                        {
                                            var task = tasks[i];
                                            if (task.IsFaulted)
                                            {
                                                InvokeError(task, task.Exception);
                                            }
                                            if (task.IsCanceled || task.IsCompleted)
                                            {
                                                tasks.RemoveAt(i);
                                            }
                                        }
                                    }
                                },
                                ex => Log.Error(ex));
    }
}

Utils.Interval is simply a helper method which repeatedly executes an action when the specified interval has elapsed.

public static void Interval(long interval, Action action, Action<Exception> errorHandler = null)
{
    var watch = Stopwatch.StartNew();
    while (true)
    {
        try
        {
            action();
            watch.Stop();
            Thread.Sleep((int)Math.Max(0, interval - watch.ElapsedMilliseconds));
            watch.Restart();
        }
        catch (Exception ex)
        {
            if (errorHandler == null)
                throw;
            errorHandler(ex);
        }
    }
}

Now, in the top level code (e.g. inside Main()), just subscribe to the Error event of TaskErrorWatcher and do whatever you want to do in case of error, e.g.

TaskErrorWatcher.Instance.Error += (_, error) => HandleError(error);

That’s it. Hope it helps!