在此代码中:
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
吗?
答案 0 :(得分:58)
我并不完全记得在哪里,但我在某处读到了新的 async / await 关键字,他们将AggregateException
打开到实际的异常中。
因此,在catch块中,您将获得实际的异常,而不是聚合的异常。这有助于我们编写更自然,更直观的代码。
还需要将现有代码更轻松地转换为使用 async / await ,其中大量代码需要特定的异常,而不是聚合异常。
- 编辑 -
知道了:
比尔瓦格纳说:(在当例外情况发生时)...当你使用await时,编译器生成的代码会解开 AggregateException并抛出基础异常。通过杠杆化 等待,您可以避免额外的工作来处理AggregateException类型 由Task.Result,Task.Wait和其他定义的Wait方法使用 任务类。这是使用等待而不是使用等待的另一个原因 基础任务方法......
答案 1 :(得分:24)
我知道这是一个已经回答的问题,但所选择的答案并非真正解决了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(即使只有一个任务引发了异常)。
希望这仍然有用。我知道我今天遇到了这个问题。
答案 2 :(得分:23)
您可以遍历所有任务以查看是否有多个任务抛出异常:
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(); });
}
答案 3 :(得分:12)
您正在考虑Task.WaitAll
- 它会抛出AggregateException
。
WhenAll只抛出它遇到的异常列表的第一个例外。
答案 4 :(得分:9)
这里有很多好的答案,但是我仍然想发表我的直言,因为我遇到了同样的问题并进行了一些研究。或跳至下面的the TLDR version。
等待task
返回的Task.WhenAll
只会抛出AggregateException
中存储的task.Exception
的第一个异常,即使有多个任务出错。
current docs for Task.WhenAll
说:
如果提供的任何任务在故障状态下完成,则 返回的任务也将在故障状态下完成,其中 异常将包含未包装集合的集合 每个提供的任务都有异常。
这是正确的,但是它没有说明等待等待返回的任务的上述“展开”行为。
我想,文档没有提及它,因为行为并非特定于Task.WhenAll
。
Task.Exception
的类型仅为AggregateException
,对于await
的延续,根据设计,它总是被解包为第一个内部异常。这在大多数情况下都很好,因为通常Task.Exception
仅包含一个内部异常。但是请考虑以下代码:
Task WhenAllWrong()
{
var tcs = new TaskCompletionSource<DBNull>();
tcs.TrySetException(new Exception[]
{
new InvalidOperationException(),
new DivideByZeroException()
});
return tcs.Task;
}
var task = WhenAllWrong();
try
{
await task;
}
catch (Exception exception)
{
// task.Exception is an AggregateException with 2 inner exception
Assert.IsTrue(task.Exception.InnerExceptions.Count == 2);
Assert.IsInstanceOfType(task.Exception.InnerExceptions[0], typeof(InvalidOperationException));
Assert.IsInstanceOfType(task.Exception.InnerExceptions[1], typeof(DivideByZeroException));
// However, the exception that we caught here is
// the first exception from the above InnerExceptions list:
Assert.IsInstanceOfType(exception, typeof(InvalidOperationException));
Assert.AreSame(exception, task.Exception.InnerExceptions[0]);
}
在这里,AggregateException
的实例被解包为它的第一个内部异常InvalidOperationException
,其方式与我们在Task.WhenAll
中获得的方式完全相同。如果我们不直接通过DivideByZeroException
,我们可能就无法观察task.Exception.InnerExceptions
。
Microsoft的Stephen Toub在the related GitHub issue中解释了此行为背后的原因:
我要说的是,它已经被深入讨论, 多年前,最初添加这些内容时。我们最初做了什么 您的建议是,WhenWhen返回的Task包含一个 包含所有异常的单个AggregateException,即 task.Exception将返回一个AggregateException包装器,该包装器 包含另一个AggregateException,然后包含实际 例外;然后在等待时,内部AggregateException 将被传播。我们收到的强烈反馈使我们 更改设计是:a)绝大多数这种情况 相当同质的例外,例如, 聚合不是那么重要,b)然后传播聚合 打破了对特定异常类型的预期, c)对于某人确实想要聚合的情况,他们可以 像我写的那样,用两行明确地我们也有广泛 关于等待行为的讨论 包含多个异常的任务,这就是我们着陆的地方。
另一件事要注意,这种展开行为很浅。也就是说,即使它恰好是另一个AggregateException.InnerExceptions
的实例,它也只会从AggregateException
中解开第一个异常并将其保留在那里。这可能会增加另一层混乱。例如,让我们像这样更改WhenAllWrong
:
async Task WhenAllWrong()
{
await Task.FromException(new AggregateException(
new InvalidOperationException(),
new DivideByZeroException()));
}
var task = WhenAllWrong();
try
{
await task;
}
catch (Exception exception)
{
// now, task.Exception is an AggregateException with 1 inner exception,
// which is itself an instance of AggregateException
Assert.IsTrue(task.Exception.InnerExceptions.Count == 1);
Assert.IsInstanceOfType(task.Exception.InnerExceptions[0], typeof(AggregateException));
// And now the exception that we caught here is that inner AggregateException,
// which is also the same object we have thrown from WhenAllWrong:
var aggregate = exception as AggregateException;
Assert.IsNotNull(aggregate);
Assert.AreSame(exception, task.Exception.InnerExceptions[0]);
Assert.IsInstanceOfType(aggregate.InnerExceptions[0], typeof(InvalidOperationException));
Assert.IsInstanceOfType(aggregate.InnerExceptions[1], typeof(DivideByZeroException));
}
因此,回到await Task.WhenAll(...)
,我个人希望能够:
AggregateException
; Task
来检查其Task.Exception
; Task.IsCanceled
),因为类似这样的操作不会这样做:Task t = Task.WhenAll(...); try { await t; } catch { throw t.Exception; }
。为此,我整理了以下扩展名:
public static class TaskExt
{
/// <summary>
/// A workaround for getting all of AggregateException.InnerExceptions with try/await/catch
/// </summary>
public static Task WithAggregatedExceptions(this Task @this)
{
// using AggregateException.Flatten as a bonus
return @this.ContinueWith(
continuationFunction: anteTask =>
anteTask.IsFaulted &&
anteTask.Exception is AggregateException ex &&
(ex.InnerExceptions.Count > 1 || ex.InnerException is AggregateException) ?
Task.FromException(ex.Flatten()) : anteTask,
cancellationToken: CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
scheduler: TaskScheduler.Default).Unwrap();
}
}
现在,以下内容以我想要的方式工作:
try
{
await Task.WhenAll(
Task.FromException(new InvalidOperationException()),
Task.FromException(new DivideByZeroException()))
.WithAggregatedExceptions();
}
catch (OperationCanceledException)
{
Trace.WriteLine("Canceled");
}
catch (AggregateException exception)
{
Trace.WriteLine("2 or more exceptions");
// Now the exception that we caught here is an AggregateException,
// with two inner exceptions:
var aggregate = exception as AggregateException;
Assert.IsNotNull(aggregate);
Assert.IsInstanceOfType(aggregate.InnerExceptions[0], typeof(InvalidOperationException));
Assert.IsInstanceOfType(aggregate.InnerExceptions[1], typeof(DivideByZeroException));
}
catch (Exception exception)
{
Trace.WriteLine($"Just a single exception: ${exception.Message}");
}
答案 5 :(得分:6)
我想我会扩展@ Richiban的答案,说你也可以通过从任务中引用它来处理catch块中的AggregateException。 E.g:
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");
}
答案 6 :(得分:-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
}
答案 7 :(得分:-3)
这对我有用
private async Task WhenAllWithExceptions(params Task[] tasks)
{
var result = await Task.WhenAll(tasks);
if (result.IsFaulted)
{
throw result.Exception;
}
}
答案 8 :(得分:-5)
试试这个代码模式:
Task task = null;
try
{
task = Task.WhenAll(...);
await task;
}
catch (AggregateException aggException)
{
var aggException = task.Exception;
...
}