Reputation: 7060
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
Upvotes: 5
Views: 1035
Reputation: 3236
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.)
Upvotes: 2
Reputation: 8847
The answer https://stackoverflow.com/a/36365489/340035 is very nice one.
The only thing I would change is the amount of characters and use the following :
await Task.WhenAll(tasks).ContinueWith(t => t.Wait());
You can find more info about exception handling decisions in this nice post: https://blogs.msdn.microsoft.com/pfxteam/2011/09/28/task-exception-handling-in-net-4-5/
Upvotes: 1
Reputation: 101553
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.
Upvotes: 3