static async void Main(string[] args)
{
Task t = new Task(() => { throw new Exception(); });
try
{
t.Start();
t.Wait();
}
catch (AggregateException e)
{
// When waiting on the task, an AggregateException is thrown.
}
try
{
t.Start();
await t;
}
catch (Exception e)
{
// When awating on the task, the exception itself is thrown.
// in this case a regular Exception.
}
}
In TPL, When throwing an exception inside a Task, it's wrapped with an AggregateException.
But the same is not happening when using the await keyword.
What is the explanation for that behavior ?
The goal is to make it look/act like the synchronous version. Jon Skeet does a great job explaining this in his Eduasync series, specifically this post:
In TPL AggregateException
is used because you can have multiple tasks in wait operation (task can have child tasks attached), so many of them can throw exceptions. Look at Exceptions in child tasks section here:
https://msdn.microsoft.com/ru-ru/library/dd997417(v=vs.110).aspx
In await
you always have just one task.
See also https://msdn.microsoft.com/ru-ru/library/dd997415(v=vs.110).aspx
Here is a good explanation in details, by Stephen Toub, why there is a difference in exception type between Task.Wait() and await:
Task Exception Handling in .NET 4.5
When designing Task.Wait in .NET 4, we chose always propagating an aggregate. That decision was influenced by the need to not overwrite details, but also by the primary use case for tasks at the time, that of fork/join parallelism, where the potential for multiple exceptions is quite common.
While similar to Task.Wait at a high level (i.e. forward progress isn’t made until the task completes), “await task” represents a very different primary set of scenarios. Rather than being used for fork/join parallelism, the most common usage of “await task” is in taking a sequential, synchronous piece of code and turning it into a sequential, asynchronous piece of code. In places in your code where you perform a synchronous operation, you replace it with an asynchronous operation represented by a task and “await” it. As such, while you can certainly use await for fork/join operations (e.g. utilizing Task.WhenAll), it’s not the 80% case. Further, .NET 4.5 sees the introduction of System.Runtime.ExceptionServices.ExceptionDispatchInfo, which solves the problem of allowing you to marshal exceptions across threads without losing exception details like stack trace and Watson buckets. Given an exception object, you pass it to ExceptionDispatchInfo.Create, which returns an ExceptionDispatchInfo object that contains a reference to the Exception object and a copy of the its details. When it’s time to throw the exception, the ExceptionDispatchInfo’s Throw method is used to restore the contents of the exception and throw it without losing the original information (the current call stack information is appended to what’s already stored in the Exception).
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. 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.
来源:https://stackoverflow.com/questions/7340309/throw-exception-inside-a-task-await-vs-wait