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

问题描述 投票:72回答:8

在这段代码中:

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();});
}

我期待WhenAll创建并抛出一个AggregateException,因为至少有一个等待它的任务抛出异常。相反,我正在收回其中一个任务抛出的单个异常。

WhenAll不总是创造一个AggregateException

.net exception asynchronous tap
8个回答
59
投票

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

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

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

- 编辑 -

得到它了:

An Async Primer by Bill Wagner

比尔瓦格纳说:(在例外情况下发生)

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


30
投票

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

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

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");
}

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

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


25
投票

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

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(); });
}

12
投票

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

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


6
投票

我想我会扩展@ 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");
}

-3
投票

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

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

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

-3
投票

这适合我

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

-5
投票

试试这个代码模式:

Task task = null;
try
{
    task = Task.WhenAll(...);
    await task;
 }
 catch (AggregateException aggException)
 {
     var aggException = task.Exception;
     ...
 }
© www.soinside.com 2019 - 2024. All rights reserved.