93

With System.Threading.Tasks.Task<TResult>, I have to manage the exceptions that could be thrown. I'm looking for the best way to do that. So far, I've created a base class that manages all the uncaught exceptions inside the call of .ContinueWith(...)

I'm wondering if there's a better way do do that. Or even if it is a good way to do that.

public class BaseClass
{
    protected void ExecuteIfTaskIsNotFaulted<T>(Task<T> e, Action action)
    {
        if (!e.IsFaulted) { action(); }
        else
        {
            Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
            {
                /* I display a window explaining the error in the GUI 
                 * and I log the error.
                 */
                this.Handle.Error(e.Exception);
            }));            
        }
    }
}   

public class ChildClass : BaseClass
{
    public void DoItInAThread()
    {
        var context = TaskScheduler.FromCurrentSynchronizationContext();
        Task.Factory.StartNew<StateObject>(() => this.Action())
                    .ContinueWith(e => this.ContinuedAction(e), context);
    }

    private void ContinuedAction(Task<StateObject> e)
    {
        this.ExecuteIfTaskIsNotFaulted(e, () =>
        {
            /* The action to execute 
             * I do stuff with e.Result
             */

        });        
    }
}
casperOne
  • 73,706
  • 19
  • 184
  • 253
JiBéDoublevé
  • 4,124
  • 4
  • 36
  • 57

2 Answers2

122

There are two ways you can do this, dependent on the version of the language you are using.

C# 5.0 and above

You can use the async and await keywords to simplify a great deal of this for you.

async and await were introduced into the language to simplify using the Task Parallel Library, preventing you from having to use ContinueWith and allowing you to continue to program in a top-down manner.

Because of this, you can simply use a try/catch block to catch the exception, like so:

try
{
    // Start the task.
    var task = Task.Factory.StartNew<StateObject>(() => { /* action */ });

    // Await the task.
    await task;
}
catch (Exception e)
{
    // Perform cleanup here.
}

Note that the method encapsulating the above must use have the async keyword applied so you can use await.

C# 4.0 and below

You can handle exceptions using the ContinueWith overload that takes a value from the TaskContinuationOptions enumeration, like so:

// Get the task.
var task = Task.Factory.StartNew<StateObject>(() => { /* action */ });

// For error handling.
task.ContinueWith(t => { /* error handling */ }, context,
    TaskContinuationOptions.OnlyOnFaulted);

The OnlyOnFaulted member of the TaskContinuationOptions enumeration indicates that the continuation should only be executed if the antecedent task threw an exception.

Of course, you can have more than one call to ContinueWith off the same antecedent, handling the non-exceptional case:

// Get the task.
var task = new Task<StateObject>(() => { /* action */ });

// For error handling.
task.ContinueWith(t => { /* error handling */ }, context, 
    TaskContinuationOptions.OnlyOnFaulted);

// If it succeeded.
task.ContinueWith(t => { /* on success */ }, context,
    TaskContinuationOptions.OnlyOnRanToCompletion);

// Run task.
task.Start();
casperOne
  • 73,706
  • 19
  • 184
  • 253
  • 1
    How would you know the exception type in the anonymous method ? If I do t.Exception , intellisense wont expose the innerexception, message ...etc properties... – guiomie Dec 05 '13 at 21:40
  • 5
    @guiomie `t` *is* the exception. – casperOne Dec 06 '13 at 12:00
  • 2
    context is not defined what is it ? – Furkan Gözükara Aug 20 '14 at 19:26
  • @MonsterMMORPG `SynchronizationContext`, if needed. – casperOne Aug 20 '14 at 19:27
  • Ty for answer why would we need it ? I mean in which case ? is this enough ? myTask.ContinueWith(t => ErrorLogger.LogError("Error happened at func_CheckWaitingToProcessPages started task and error: " + t), TaskContinuationOptions.OnlyOnFaulted); – Furkan Gözükara Aug 20 '14 at 21:28
  • @MonsterMMORPG In case you needed to do something on error that was bound to a `SynchronizationContext`. For example, if you needed to display the exception details in the UI, then you'd need the UI's `SynchronizationContext` to make calls to update UI elements. Note that now, you should use `async`/`await`, which will capture the `SynchronizationContext` for you (unless you call `ConfigureAwait(false)`). – casperOne Aug 21 '14 at 14:05
  • What is `context` in your example? I Target .NET 4.5.2 and I'm not sure we use that same overload. (`t` is **not** the exception but a `Task`). Again, maybe it was changed in the versions. – gdoron May 14 '15 at 15:03
  • @gdoron If you're on .NET 4.5.2, then this question is not really the approach you want, you should be looking at `async`/`await`. – casperOne May 14 '15 at 15:23
  • @casperOne, In my real code I use `async / await`, I only played with ContinueWith to force race condition for testing purposes. – gdoron May 14 '15 at 15:51
  • @gdoron `context` is the `SynchronizationContext` needed for that overload of the method, it's assumed to come from elsewhere. – casperOne May 15 '15 at 01:14
5

You can create some custom Task factory, which will produce Tasks with exception handling processing embedded. Something like this:

using System;
using System.Threading.Tasks;

class FaFTaskFactory
{
    public static Task StartNew(Action action)
    {
        return Task.Factory.StartNew(action).ContinueWith(
            c =>
            {
                AggregateException exception = c.Exception;

                // Your Exception Handling Code
            },
            TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously
        ).ContinueWith(
            c =>
            {
                // Your task accomplishing Code
            },
            TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously
        );
    }

    public static Task StartNew(Action action, Action<Task> exception_handler, Action<Task> completion_handler)
    {
        return Task.Factory.StartNew(action).ContinueWith(
            exception_handler,
            TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously
        ).ContinueWith(
            completion_handler,
            TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously
        );
    }
};

You can forget about exceptions processing for Tasks produced from this factory in your client code. In the same time you still can wait finishing of such Tasks or use them in Fire-and-Forget style:

var task1 = FaFTaskFactory.StartNew( () => { throw new NullReferenceException(); } );
var task2 = FaFTaskFactory.StartNew( () => { throw new NullReferenceException(); },
                                      c => {    Console.WriteLine("Exception!"); },
                                      c => {    Console.WriteLine("Success!"  ); } );

task1.Wait(); // You can omit this
task2.Wait(); // You can omit this

But if be honest I'm not really sure why you want to have completion handling code. In any case this decision depends on the logic of your application.

ZarathustrA
  • 3,434
  • 32
  • 28