Michael Ray Lovett
Michael Ray Lovett

Reputation: 7060

.Net how to report all exceptions that are thrown during Task.WhenAll()?

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

Answers (3)

Ulf Åkerstedt
Ulf Åkerstedt

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

Alexander Bartosh
Alexander Bartosh

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

Evk
Evk

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

Related Questions