Question

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

Dans TPL, Lorsque vous jetez une exception à l'intérieur d'une tâche, il est enveloppé d'un AggregateException.
Mais même ne se produit pas lorsque vous utilisez le await mot-clé.
Quelle est l'explication de ce comportement?

Était-ce utile?

La solution

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:

http://codeblog.jonskeet.uk/2011/06/22/eduasync-part-11-more-sophisticated-but-lossy-exception-handling/

Autres conseils

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top