异步方法似乎始终将其异常捕获到该方法返回的Task
中,包括在第一个await
之前抛出的。换句话说,下面的代码按预期工作,在Task.WhenAll
:
async Task DoSomethingAsync(int i)
{
if (i == 2) throw new InvalidOperationException();
await Task.Delay(1000);
}
...
var tasks = new List<Task>();
for(int i = 0; i < 3; ++i)
{
var t = DoSomethingAsync(i); // no awaits here
tasks.Add(t);
}
// Wait for all tasks
await Task.WhenAll(tasks); // throws InvalidOperation when the other 2 tasks succeed
问题:
异步方法的这种行为是语言规范的一部分还是它在当前版本的.NET中实现的方式?我可以在代码中依赖此行为吗?
答案 0 :(得分:4)
异步方法的这种行为是否是语言规范的一部分
是的。来自C#5规范的第10.15.1节:
- 如果函数体因未捕获的异常(第8.9.5节)而终止,则异常将记录在返回任务中,该任务将进入故障状态。
第10.15.2节详细介绍了返回void
的异步方法:
如果async函数的返回类型为void,则评估与以上方式的不同之处在于:由于未返回任何任务,因此该函数会将当前线程的同步上下文的完成和异常通知。同步上下文的确切定义是依赖于实现的,但是表示当前线程正在运行的“where”。当评估void返回异步函数开始,成功完成或导致未捕获的异常被抛出时,将通知同步上下文。
不可否认,我认为明确地表示调用本身不会抛出,但我相信这是对规范的预期解释。
如果想要抛出一个方法,如果不满足(比方说)前提条件,然后使用正常的异步行为,你可以采用与迭代器块相同的方法:
public Task Foo(int someParameter)
{
// Check preconditions here and throw - note that this isn't an
// async method, so the exceptions will be thrown synchronously.
return FooImpl(someParameter);
}
private async Task FooImpl(int someParameter)
{
// Assume everything is valid now.
// Normal async method implementation.
}