I am using TPL and async/await to build async API on top of webclient for my applications. And few of the places (generally where I need to run bunch of async tasks and wait for all of them in the end), am following code snippet. I just want to make sure I get it correct as even though writing async code is relatively easy with TPL and async/await debugging/trouble shooting is still challenging (both interactive debugging and troubleshooting issues at customer site) - so want to get it right.
My Goal: Able to capture the exception generated from the original task, continuation tasks as well as child tasks, so that I can handle it (if I need to). I don't want any exception to be fir and forgotten.
Basic principals I used: 1. .net framework ensures the exception will be attached to task 2. Try/catch block can be applied to async/await to give illusion/readability of sync code (references: http://channel9.msdn.com/Events/TechDays/Techdays-2014-the-Netherlands/Async-programming-deep-dive, http://blogs.msdn.com/b/ericlippert/archive/2010/11/19/asynchrony-in-c-5-part-seven-exceptions.aspx, http://msdn.microsoft.com/en-us/library/dd537614.aspx etc.)
Question I would like to get ratification that the desired goal (that I can capture exception from original, continuation and child tasks) has been achieved, and is there any improvements I can do to the sample:
For ex, will there be a case where one of the composed tasks (for ex, unwrapped proxy task) won't be activated at all (waitforactivation state), so that waitall may simply wait for the task to start? My understanding is that these cases should never happens as the continuation task always executes, and returns a task which has been tracked by proxy using wnwrap. As long as I follow similar pattern in all layers and apis, the pattern should capture all the aggregated exceptions in the chained tasks.
Note: essentially am looking for suggestions like avoiding dummy task am creating in continuation task if original task status is not ran to completion, or using attach to parent so that i can wait only on parent etc. to see all possibilities so that i can pick the best option as this pattern am heavily relying in my app for error handling.
static void SyncAPIMethod(string[] args)
{
try
{
List<Task> composedTasks = new List<Task>();
//the underlying async method follow the same pattern
//either they chain the async tasks or, uses async/await
//wherever possible as its easy to read and write the code
var task = FooAsync();
composedTasks.Add(task);
var taskContinuation = task.ContinueWith(t =>
{
//Intentionally not using TaskContinuationOptions, so that the
//continuation task always runs - so that i can capture exception
//in case something is wrong in the continuation
List<Task> childTasks = new List<Task>();
if (t.Status == TaskStatus.RanToCompletion)
{
for (int i = 1; i <= 5; i++)
{
var childTask = FooAsync();
childTasks.Add(childTask);
}
}
//in case of faulted, it just returns dummy task whose status is set to
//'RanToCompletion'
Task wa = Task.WhenAll(childTasks);
return wa;
});
composedTasks.Add(taskContinuation);
//the unwrapped task should capture the 'aggregated' exception from childtasks
var unwrappedProxyTask = taskContinuation.Unwrap();
composedTasks.Add(unwrappedProxyTask);
//waiting on all tasks, so the exception will be thrown if any of the tasks fail
Task.WaitAll(composedTasks.ToArray());
}
catch (AggregateException ag)
{
foreach(Exception ex in ag.Flatten().InnerExceptions)
{
Console.WriteLine(ex);
//handle it
}
}
}
Best Regards.