I have a Forms app that at several points does an
await Task.WhenAll(list_of_tasks)
My understanding is that in this scenario, if one or more tasks in list_of_tasks throws an exception, the "await" will also propagate just one of those exceptions. (You can examine each task to see why it failed, however)
My question is this: if you have an application wide exception handler which logs all exceptions to a log, how could it log information for each of the exceptions thrown in the list_of_tasks? (Given that the exception handler will just be presented with one exception emitted from the await, and that exception is not something like an AggregateException which tells you about the other exceptions)
Michael
WhenAll(tasklist)", it will throw an exception if any of the tasks are faulted.
WhenAll creates a task that will complete when all of the supplied tasks have been completed. It's pretty straightforward what this method does, it simply receives a list of Tasks and returns a Task when all of the received Tasks completes.
When using await, it's going to unwrap the first exception and return it, that's why we don't hit the catch (AggregateException e) line. But if we use something like the below code sample, we catch the AggregateException , note that it's not a good idea since we're blocking the running thread.
So as you probably know already, await unwraps AggregateException for your convenience, because Task.Exception property is of AggregateException type and if it didn't unwrap - you would have to always catch AggregateException when awaiting some task, then bother with unwrapping yourself.
With Task.WhenAll I think indeed such behavior is not what you want. All other exceptions are just swallowed (so considered observed and will not get to UnobservedTaskException handler for example). If it's important for you to log all exceptions in such cases you can write some extension method, like this:
static class TaskExtensions {
static async Task WhenAllAggregate(params Task[] tasks) {
var all = Task.WhenAll(tasks);
try {
await all;
}
catch {
// swallow caugth exception but throw all
throw all.Exception;
}
}
}
Then just log aggregate exceptions in your application wide handler as usual.
Jon Skeet provides a sketch for a solution as an extension method:
public static Task PreserveMultipleExceptions(this Task originalTask)
{
var tcs = new TaskCompletionSource<object>();
originalTask.ContinueWith(task => {
switch (task.Status) {
case TaskStatus.Canceled:
tcs.SetCanceled();
break;
case TaskStatus.RanToCompletion:
tcs.SetResult(null);
break;
case TaskStatus.Faulted:
tcs.SetException(originalTask.Exception);
break;
}
}, TaskContinuationOptions.ExecuteSynchronously);
return tcs.Task;
}
which you can use such:
var whenAllTask = Task.WhenAll(tasks).PreserveMultipleExceptions();
await whenAllTask; // Note that the exception isn't thrown until the task has been awaited!
Or if you prefer (in the current lack of type extension methods):
public static Task WhenAllPreserveMultipleExceptions(this IEnumerable<Task> tasks)
{
var aggregateTask = Task.WhenAll(tasks);
var tcs = new TaskCompletionSource<object>();
aggregateTask.ContinueWith(task => {
switch (task.Status)
{
case TaskStatus.Canceled:
tcs.SetCanceled();
break;
case TaskStatus.RanToCompletion:
tcs.SetResult(null);
break;
case TaskStatus.Faulted:
tcs.SetException(aggregateTask.Exception);
break;
}
}, TaskContinuationOptions. ExecuteSynchronously);
return tcs.Task;
}
which you can use such:
var whenAllTask = tasks.WhenAllPreserveMultipleExceptions();
await whenAllTask; // Note that the exception isn't thrown until the task has been awaited!
(A possible problem with the currently accepted solution is that it will throw when the method is called and not when it is awaited which might not be what you want, and doesn't mimic Task.WhenAll.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With