I Want Await to Throw Aggregateexception, Not Just the First Exception

I want await to throw AggregateException, not just the first Exception

I disagree with the implication in your question title that await's behavior is undesired. It makes sense in the vast majority of scenarios. In a WhenAll situation, how often do you really need to know all of the error details, as opposed to just one?

The main difficulty with AggregateException is the exception handling, i.e., you lose the ability to catch a particular type.

That said, you can get the behavior you want with an extension method:

public static async Task WithAggregateException(this Task source)
{
try
{
await source.ConfigureAwait(false);
}
catch
{
// source.Exception may be null if the task was canceled.
if (source.Exception == null)
throw;

// EDI preserves the original exception's stack trace, if any.
ExceptionDispatchInfo.Capture(source.Exception).Throw();
}
}

AggregateException from Task.WhenAll only contains first exception when awaited

What you are observing is the behavior of the await operator, not the behavior of the Task.WhenAll method. If you are interested in why the await behaves this way, you could read this article from the early days of async/await:

Having the choice of always throwing the first or always throwing an aggregate, for await we opt to always throw the first. This doesn’t mean, though, that you don’t have access to the same details. In all cases, the Task’s Exception property still returns an AggregateException that contains all of the exceptions, so you can catch whichever is thrown and go back to consult Task.Exception when needed. Yes, this leads to a discrepancy between exception behavior when switching between task.Wait() and await task, but we’ve viewed that as the significant lesser of two evils.

In case you would like to implement a method similar in behavior to Task.WhenAll,
but without losing the convenience of the async/await machinery, it is tricky but there are workarounds available here.

How to return AggregateException from async method

async methods are designed to only every set at most a single exception on the returned task, not multiple.

This leaves you with two options, you can either not use an async method to start with, instead relying on other means of performing your method:

public Task MyWhenAll(Task t1, Task t2)
{
return Task.Delay(TimeSpan.FromMilliseconds(100))
.ContinueWith(_ => Task.WhenAll(t1, t2))
.Unwrap();
}

If you have a more complex method that would be harder to write without using await, then you'll need to unwrap the nested aggregate exceptions, which is tedious, although not overly complex, to do:

    public static Task UnwrapAggregateException(this Task taskToUnwrap)
{
var tcs = new TaskCompletionSource<bool>();

taskToUnwrap.ContinueWith(task =>
{
if (task.IsCanceled)
tcs.SetCanceled();
else if (task.IsFaulted)
{
if (task.Exception is AggregateException aggregateException)
tcs.SetException(Flatten(aggregateException));
else
tcs.SetException(task.Exception);
}
else //successful
tcs.SetResult(true);
});

IEnumerable<Exception> Flatten(AggregateException exception)
{
var stack = new Stack<AggregateException>();
stack.Push(exception);
while (stack.Any())
{
var next = stack.Pop();
foreach (Exception inner in next.InnerExceptions)
{
if (inner is AggregateException innerAggregate)
stack.Push(innerAggregate);
else
yield return inner;
}
}
}

return tcs.Task;
}

Why doesn't await on Task.WhenAll throw an AggregateException?

I don't exactly remember where, but I read somewhere that with new async/await keywords, they unwrap the AggregateException into the actual exception.

So, in catch block, you get the actual exception and not the aggregated one. This helps us write more natural and intuitive code.

This was also needed for easier conversion of existing code into using async/await where the a lot of code expects specific exceptions and not aggregated exceptions.

-- Edit --

Got it:

An Async Primer by Bill Wagner

Bill Wagner said: (in When Exceptions Happen)

...When you use await, the code generated by the compiler unwraps the
AggregateException and throws the underlying exception. By leveraging
await, you avoid the extra work to handle the AggregateException type
used by Task.Result, Task.Wait, and other Wait methods defined in the
Task class. That’s another reason to use await instead of the
underlying Task methods....

Wait for async Task without wrapping exceptions in AggregateException


I am going to use these in a command line application. So I need to call them synchronously a lot.

No, you don't. You can use async-await in a console application, you just need to make an async to sync transition at the very top. And you can do that by using Wait():

public static void Main()
{
MainAsync().Wait();
}

public static async Task MainAsync()
{
var datastore = …;
await datastore.SaveAsync();
}

Usually, combining await with Wait() is a bad idea (it can cause deadlocks), but it's the right solution here.

Note that if SaveAsync() throws an exception and you don't catch it, it will be rethrown as AggregateException from the Wait(). But you can catch it as the original exception in MainAsync() (because it doesn't use Wait()).

If you really wanted to get the first exception thrown directly, you could do something similar to what await does: task.GetAwaiter().GetResult(). Note that if the Task contains more than one exception, you will get only the first one (but the same applies to await).

Since C# 7.1, you can make your Main method async and the compiler will write the transition code for you:

public static async Task Main()
{
var datastore = …;
await datastore.SaveAsync();
}

When I use a method returning Task<TResult>, task.Result throws AggregateException even though there are no continuation tasks set. Why is this happening?

This has nothing to do with continuations. A single Task can represent multiple operations, and each of them can throw an exception. Because of that, Task methods always throw the exceptions wrapped in an AggregateException.

I also have tried task.RunSynchronously()

That doesn't make any sense. RunSynchronously() can only be used on Tasks that were created using the Task constructor. That's not the case here, so you can't use it. Tasks returned from async methods are always already started.

Will awaiting multiple tasks observe more than the first exception?

Task.WhenAll returns a task and like all tasks the Exception property holds an AggregateException that combines all exceptions.

When you await such a task only the first exception will actually be thrown.

... Whether because of child tasks that fault, or because of combinators like Task.WhenAlll, a single task may represent multiple operations, and more than one of those may fault. In such a case, and with the goal of not losing exception information (which can be important for post-mortem debugging), we want to be able to represent multiple exceptions, and thus for the wrapper type we chose AggregateException.

... Given that, and again having the choice of always throwing the first or always throwing an aggregate, for “await” we opt to always throw the first

from Task Exception Handling in .NET 4.5

It's up to you to choose if you want to handle just the first using await task; (true in most cases) or handle all using task.Exception (as in my example below), but in both cases a and b would not raise an UnobservedTaskException.

var task = Task.WhenAll(a, b);
try
{
await task;
}
catch
{
Trace.WriteLine(string.Join(", ", task.Exception.Flatten().InnerExceptions.Select(e => e.Message)));
}

How to throw/catch the inner exception from a task?

When exception is thrown in task then using Result property you will get AggregateException where the real exception is in InnerException property of this AggregateException object (your exception is wrapped by AggregateException).

To get the true exception (unwrapped exception) you can use GetAwaiter().GetResult():

var result = taskThatThrowsException.GetAwaiter().GetResult();

You can also use Result property but then you should specify some condition for exception handling;

try
{
var result = taskThatThrowsException.Result;
}
catch (AggregateException ex) when (ex.InnerException is MyException myException)
{
// use myException - it is your true unwrapped exception
}

But you should NOT block asynchronous code - you should asynchronously wait - use await and you will get your true unwrapped exception also:

var result = await taskThatThrowsException;


Related Topics



Leave a reply



Submit