views:

267

answers:

1

I'm working on a Web Hook in .NET 4.0 that will run a lambda asynchronously and then post the result to a given URI when it is finished.

I've got that to work, but now I want the Task to handle any exceptions that are thrown, and am finding it difficult to stop them from reaching the parent.

Here's part of my code:

private readonly Func<T> _startTask;
private readonly string _responseUri;

public Task<T> Begin()
{
    var task = new Task<T>(_startTask);
    task.ContinueWith<T>(End);
    task.Start();
    return task;
}

private T End(Task<T> task)
{
    if (task.IsFaulted)
    {
        return HandleException(task);
    }

    var result = task.Result;
    WebHookResponse.Respond(result, _responseUri);
    return result;
}

private T HandleException(Task<T> task)
{
    WebHookResponse.HandleException(task.Exception.InnerException, _responseUri);
    return null;
}

An alternative version that I have tried calls ContinueWith() twice to register one continuation to run OnlyOnRanToCompletion and one to run OnlyOnFaulted. (I'm not sure if calling ContinueWith() twice is correct.):

public Task<T> Begin()
{
    var task = new Task<T>(_startTask);
    task.ContinueWith<T>(End, TaskContinuationOptions.OnlyOnRanToCompletion);
    task.ContinueWith<T>(HandleException, TaskContinuationOptions.OnlyOnFaulted);
    task.Start();
    return task;
}

private T End(Task<T> task)
{
    var result = task.Result;
    WebHookResponse.Respond(result, _responseUri);
    return result;
}

private T HandleException(Task<T> task)
{
    WebHookResponse.HandleException(task.Exception.InnerException, _responseUri);
    return null;
}

So basically I want a way for each Task handle its own exceptions via a continuation function. As it stands the HandlException continuation function is never being called in either of the above examples.

I am causing the exceptions in a test case, and I should mention that I am using a Tasks.WaitAll(tasks); call on an array of Tasks to make sure all of the tasks are complete before making my assertions, and I am not sure if that call makes a difference to how exceptions are handled by the Tasks. Currently WaitAll throws an AggregationException which aggregates the exceptions for each of the Tasks because they aren't being handled by the HandleException continuation function.

+2  A: 

I think you fell into an old trap. Did you ever make a mistake like

string s = "Hello";
s.ToUpper();
Console.WrtieLine(s);

I think you should be using:

var task = new Task<T>(_startTask);
task = task.ContinueWith<T>(End, TaskContinuationOptions.OnlyOnRanToCompletion);
task = task.ContinueWith<T>(HandleException, TaskContinuationOptions.OnlyOnFaulted);
task.Start();

(Untested though) That's why you usualyy see the 'fluent' notation:

t.Continue(...).Continue(...).Start();
Henk Holterman
Thanks Henk, can't believe I missed that. I've changed to `Task.Factory.StartNew(_startTask).ContinueWith<T>(HandleException, TaskContinuationOptions.OnlyOnFaulted).ContinueWith<T>(End, TaskContinuationOptions.OnlyOnRanToCompletion);` but `HandleException` still doesn't run. The top version of my code works and the exceptions are handled by checking `task.IsFaulted`, but the second one doesn't because `HandleException` is never called. Maybe I can't call `ContinueWith` twice?
Martin Owen
@Martin, Yes, I think the chaining is the problem. Either `End` is called with a 'Fault' status, or with a null parameter.
Henk Holterman
Is there no way to register both an OnlyOnRanToCompletion continuation and a OnlyOnFaulted continuation?
Martin Owen
@Martin: No. Look on [this page](http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcontinuationoptions.aspx), and note all the "This option is not valid for multi-task continuations" specifications. I think that's your problem.
Henk Holterman
I think you are right, I did see that documentation but wasn't sure what a "multi-task continuation" was. I'll just handle everything within the End continuation as in my first piece of code. Thanks.
Martin Owen