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 cont开发者_开发技巧inuation 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.
A task continuation that observes the task's exception doesn't handle the exception. It still happens on wherever you wait on the task to finish.
You said you were calling WaitAll(tasks) before asserting. I'm betting that your continuation would have run if you gave it enough time, but the exception on WaitAll() is usually going to occur before your continuation runs. So your asserts probably failed before your continuation was given a chance to finish its work.
Perhaps, to the answer of Henk Holterman, the order makes a difference. That is,
var task = new Task<T>(_startTask);
task = task.ContinueWith<T>(HandleException, TaskContinuationOptions.OnlyOnFaulted);
task = task.ContinueWith<T>(End, TaskContinuationOptions.OnlyOnRanToCompletion);
task.Start();
would assure that HandleException would run when necessary.
I use this approach because it provides for a nice declarative fluent coding style and does not litter your code with the exception handling aspects.
class Program
{
static void Main()
{
Task.Factory.StartNew(
() =>
{
throw new Exception();
})
.Success(() => Console.WriteLine("Works"))
.Fail((ex) => Console.WriteLine("Fails")).Wait();
Console.ReadKey();
}
}
public static class TaskExtensions
{
public static Task Success(this Task task, Action onSuccess)
{
task.ContinueWith((t) =>
{
if (!t.IsFaulted)
{
onSuccess();
}
});
return task;
}
public static Task Fail(this Task task, Action<Exception> onFail)
{
return task.ContinueWith(
(t) =>
{
if (t.IsFaulted)
{
t.Exception.Handle(x => true);
onFail(t.Exception.Flatten());
}
});
}
}
精彩评论