.net 为什么不等待 Task.WhenAll 抛出 AggregateException?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12007781/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 16:34:19  来源:igfitidea点击:

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

.netexceptionasynchronoustap

提问by Michael Ray Lovett

In this code:

在这段代码中:

private async void button1_Click(object sender, EventArgs e) {
    try {
        await Task.WhenAll(DoLongThingAsyncEx1(), DoLongThingAsyncEx2());
    }
    catch (Exception ex) {
        // Expect AggregateException, but got InvalidTimeZoneException
    }
}

Task DoLongThingAsyncEx1() {
    return Task.Run(() => { throw new InvalidTimeZoneException(); });
}

Task DoLongThingAsyncEx2() {
    return Task.Run(() => { throw new InvalidOperation();});
}

I expected WhenAllto create and throw an AggregateException, since at least one of the tasks it was awaiting on threw an exception. Instead, I'm getting back a single exception thrown by one of the tasks.

我希望WhenAll创建并抛出一个AggregateException,因为它等待的任务中至少有一个抛出了异常。相反,我得到了其中一项任务抛出的单个异常。

Does WhenAllnot always create an AggregateException?

难道WhenAll不总是创造AggregateException

采纳答案by decyclone

I don't exactly remember where, but I read somewhere that with new async/awaitkeywords, they unwrap the AggregateExceptioninto the actual exception.

我不完全记得在哪里,但我在某处读到了新的async/await关键字,它们将其解包AggregateException到实际的异常中。

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

因此,在 catch 块中,您会得到实际的异常而不是聚合的异常。这有助于我们编写更自然和直观的代码。

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

这也是为了更容易地将现有代码转换为使用async/await,其中许多代码需要特定异常而不是聚合异常。

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

比尔·瓦格纳 (Bill Wagner) 说:(在发生异常时

...当您使用await 时,编译器生成的代码会解开AggregateException 并抛出底层异常。通过利用 await,您可以避免处理 Task.Result、Task.Wait 和 Task 类中定义的其他 Wait 方法使用的 AggregateException 类型的额外工作。这是使用 await 而不是底层 Task 方法的另一个原因......

回答by Richiban

I know this is a question that's already answered but the chosen answer doesn't reallysolve the OP's problem, so I thought I would post this.

我知道这是一个已经回答的问题,但选择的答案并没有真正解决 OP 的问题,所以我想我会发布这个。

This solution gives you the aggregate exception (i.e. allthe exceptions that were thrown by the various tasks) and doesn't block (workflow is still asynchronous).

此解决方案为您提供聚合异常(即各种任务抛出的所有异常)并且不会阻塞(工作流仍然是异步的)。

async Task Main()
{
    var task = Task.WhenAll(A(), B());

    try
    {
        var results = await task;
        Console.WriteLine(results);
    }
    catch (Exception)
    {
        if (task.Exception != null)
        {
            throw task.Exception;
        }
    }
}

public async Task<int> A()
{
    await Task.Delay(100);
    throw new Exception("A");
}

public async Task<int> B()
{
    await Task.Delay(100);
    throw new Exception("B");
}

The key is to save a reference to the aggregate task before you await it, then you can access its Exception property which holds your AggregateException (even if only one task threw an exception).

关键是在等待聚合任务之前保存对聚合任务的引用,然后您可以访问它的 Exception 属性,该属性保存您的 AggregateException (即使只有一个任务抛出异常)。

Hope this is still useful. I know I had this problem today.

希望这仍然有用。我知道我今天遇到了这个问题。

回答by jgauffin

You can traverse all tasks to see if more than one have thrown an exception:

您可以遍历所有任务以查看是否有多个任务抛出异常:

private async Task Example()
{
    var tasks = new [] { DoLongThingAsyncEx1(), DoLongThingAsyncEx2() };

    try 
    {
        await Task.WhenAll(tasks);
    }
    catch (Exception ex) 
    {
        var exceptions = tasks.Where(t => t.Exception != null)
                              .Select(t => t.Exception);
    }
}

private Task DoLongThingAsyncEx1()
{
    return Task.Run(() => { throw new InvalidTimeZoneException(); });
}

private Task DoLongThingAsyncEx2()
{
    return Task.Run(() => { throw new InvalidOperationException(); });
}

回答by Daniel ?mon

Just thought I'd expand on @Richiban's answer to say that you can also handle the AggregateException in the catch block by referencing it from the task. E.g:

只是想我会扩展@Richiban 的回答,说您还可以通过从任务中引用它来处理 catch 块中的 AggregateException。例如:

async Task Main()
{
    var task = Task.WhenAll(A(), B());

    try
    {
        var results = await task;
        Console.WriteLine(results);
    }
    catch (Exception ex)
    {
        // This doesn't fire until both tasks
        // are complete. I.e. so after 10 seconds
        // as per the second delay

        // The ex in this instance is the first
        // exception thrown, i.e. "A".
        var firstExceptionThrown = ex;

        // This aggregate contains both "A" and "B".
        var aggregateException = task.Exception;
    }
}

public async Task<int> A()
{
    await Task.Delay(100);
    throw new Exception("A");
}

public async Task<int> B()
{
    // Extra delay to make it clear that the await
    // waits for all tasks to complete, including
    // waiting for this exception.
    await Task.Delay(10000);
    throw new Exception("B");
}

回答by Mohit Datta

You're thinking of Task.WaitAll- it throws an AggregateException.

你在想Task.WaitAll- 它会抛出一个AggregateException.

WhenAll just throws the first exception of the list of exceptions it encounters.

WhenAll 只是抛出它遇到的异常列表中的第一个异常。

回答by Nebula

In your code, the first exception is returned by design as explained at http://blogs.msdn.com/b/pfxteam/archive/2011/09/28/task-exception-handling-in-net-4-5.aspx

在您的代码中,第一个异常由设计返回,如 http://blogs.msdn.com/b/pfxteam/archive/2011/09/28/task-exception-handling-in-net-4-5 所述。 aspx

As for your question, you will get the AggreateException if you write code like this:

至于你的问题,如果你写这样的代码,你会得到 AggreateException:

try {
    var result = Task.WhenAll(DoLongThingAsyncEx1(), DoLongThingAsyncEx2()).Result; 
}
catch (Exception ex) {
    // Expect AggregateException here
} 

回答by Alexey Kulikov

This works for me

这对我有用

private async Task WhenAllWithExceptions(params Task[] tasks)
{
    var result = await Task.WhenAll(tasks);
    if (result.IsFaulted)
    {
                throw result.Exception;
    }
}