Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.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

like image 609
Michael Ray Lovett Avatar asked Apr 01 '16 20:04

Michael Ray Lovett


People also ask

Does task WhenAll throw exception?

WhenAll(tasklist)", it will throw an exception if any of the tasks are faulted.

How does task WhenAll work?

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.

Does await throw AggregateException?

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.


2 Answers

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.

like image 162
Evk Avatar answered Sep 24 '22 10:09

Evk


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.)

like image 31
Ulf Åkerstedt Avatar answered Sep 22 '22 10:09

Ulf Åkerstedt