Mic*_*ett 83 .net asynchronous exception tap
在这段代码中:
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();});
}
Run Code Online (Sandbox Code Playgroud)
我期望WhenAll创建并抛出一个AggregateException,因为至少有一个等待抛出异常的任务.相反,我正在收回其中一个任务抛出的单个异常.
难道WhenAll不总是创造AggregateException?
dec*_*one 64
我并不完全记得在哪里,但我在某处读到了新的async/await关键字,他们将其解包AggregateException到实际的异常中.
因此,在catch块中,您将获得实际的异常,而不是聚合的异常.这有助于我们编写更自然,更直观的代码.
这也是为了更容易地将现有代码转换为使用async/await所需要的,其中许多代码需要特定的异常而不是聚合的异常.
- 编辑 -
得到它了:
比尔瓦格纳说:(在例外情况下发生)
...当您使用await时,编译器生成的代码会解包AggregateException并抛出基础异常.通过利用await,您可以避免额外的工作来处理Task.Result,Task.Wait和Task类中定义的其他Wait方法使用的AggregateException类型.这是使用await而不是底层Task方法的另一个原因....
Ric*_*ban 38
我知道这是一个已经回答的问题,但所选择的答案并没有真正解决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");
}
Run Code Online (Sandbox Code Playgroud)
关键是在等待它之前保存对聚合任务的引用,然后您可以访问其Exception属性,该属性保存您的AggregateException(即使只有一个任务引发了异常).
希望这仍然有用.我知道我今天遇到了这个问题.
jga*_*fin 28
您可以遍历所有任务以查看是否有多个任务抛出异常:
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(); });
}
Run Code Online (Sandbox Code Playgroud)
nos*_*tio 24
这里有很多很好的答案,但我仍然想发表我的咆哮,因为我刚刚遇到了同样的问题并进行了一些研究。或者跳到下面的 TLDR 版本。
等待task返回的 byTask.WhenAll只会抛出AggregateException存储在 中的第一个异常task.Exception,即使多个任务出现故障也是如此。
如果任何提供的任务在故障状态下完成,则返回的任务也将在故障状态下完成,其异常将包含来自每个提供的任务的未包装异常集的聚合。
这是正确的,但它没有说明前面提到的等待返回任务时的“解包”行为。
我想,文档没有提到它,因为这种行为不是特定于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]);
}
Run Code Online (Sandbox Code Playgroud)
在这里,一个 的实例AggregateException以InvalidOperationException与我们可能在Task.WhenAll. DivideByZeroException如果我们不task.Exception.InnerExceptions直接通过,我们可能无法观察。
微软的Stephen Toub在相关的 GitHub 问题中解释了这种行为背后的原因:
我试图说明的一点是,几年前最初添加这些内容时,已经对其进行了深入讨论。我们最初按照您的建议进行操作,从 WhenAll 返回的 Task 包含一个包含所有异常的 AggregateException,即 task.Exception 将返回一个 AggregateException 包装器,其中包含另一个 AggregateException ,然后包含实际异常;然后当它被等待时,内部的 AggregateException 将被传播。我们收到的导致我们更改设计的强烈反馈是 a) 绝大多数此类情况都有相当同质的异常,因此在聚合中传播所有内容并不那么重要,b) 传播聚合然后破坏了对捕获的预期对于特定的异常类型,和 c) 对于有人确实想要聚合的情况,他们可以像我写的那样用两行明确地这样做。我们还就包含多个异常的任务的 await 的行为进行了广泛的讨论,这就是我们着陆的地方。
需要注意的另一件重要事情是,这种展开行为是浅薄的。即,它只会解开第一个异常AggregateException.InnerExceptions并将其留在那里,即使它恰好是 another 的一个实例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));
}
Run Code Online (Sandbox Code Playgroud)
所以,回到await Task.WhenAll(...),我个人想要的是能够:
AggregateException如果一个或多个任务共同抛出了多个异常,则获取一个;Taskonly 以检查其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();
}
}
Run Code Online (Sandbox Code Playgroud)
现在,以下工作按我想要的方式工作:
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}");
}
Run Code Online (Sandbox Code Playgroud)
小智 11
你在考虑Task.WaitAll- 它会抛出一个AggregateException.
WhenAll只抛出它遇到的异常列表的第一个异常.
小智 9
我想我会扩展@ 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");
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
33163 次 |
| 最近记录: |