异步任务方法永远不会抛出?

时间:2016-05-28 19:24:52

标签: c# exception-handling async-await

异步方法似乎始终将其异常捕获到该方法返回的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中实现的方式?我可以在代码中依赖此行为吗?

1 个答案:

答案 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.
}