13

Task 或 Task<TResult> 对象是可等待的,因此我们可以对返回值为 Task 或 Task<TResult> 的对象使用 await 键。Task 或 Task<TResult> 是最常用的可等待对象。

我们也可以定义自己的等待对象。该对象应具有以下条件。

  1. 它有一个 GetAwaiter() 方法(实例方法或扩展方法);
  2. 它的 GetAwaiter() 方法返回一个等待者。一个对象是一个等待者,如果:
    • 它实现了 INotifyCompletion 或 ICriticalNotifyCompletion 接口;
    • 它有一个 IsCompleted,它有一个 getter 并返回一个布尔值;
    • 它有一个 GetResult() 方法,该方法返回 void 或结果。

我的问题是,为什么微软没有提供一个接口来约束这些等待对象?当前实现等待对象的方法有点复杂。

4

3 回答 3

13

最好在 Lucian Wischik 的博客文章Why must async methods return Task?中回答。

总而言之(我不是在做博客文章正义,你应该阅读它),问题是Task已经存在,所以引入一个接口意味着

  • 所有内部方法都需要更改为接口,这是一个中断更改,因此框架人们几乎不可能愿意这样做。
  • 作为一个程序员,你会经常需要决定是要返回Task还是要返回接口,这个决定并不重要。
  • 编译器总是需要一个具体的类型,所以即使你从一个方法返回了一个接口,它仍然会被编译为Task.

上面的影响是如此巨大,以至于提供一个接口是没有意义的。

于 2012-12-28T06:04:17.270 回答
5

这与他们为关键字所做的一致(请参阅C# 语言规范“foreach 语句”的foreach第 8.8.4 节)。

基本上,它是鸭式的;如果该类型实现了一个MoveNext方法和一个Current属性,这就是 C# 编译器知道如何迭代对象公开的序列所需的全部内容。

这也适用于集合初始化器(请参阅 C# 语言规范“集合初始化器”的第 7.6.10.3 节);唯一的要求是该类型实现了System.Collections.IEnumerable接口并具有Add方法。

也就是说,await关键字只是遵循先前的先例,不需要特定的接口实现(尽管接口提供这些方法,如果您选择使用它们),只是编译器可以识别的方法模式。

于 2012-12-28T05:44:53.373 回答
1

我认为主要原因是您在第 1 点中所说的

实例方法或扩展方法

简单地说,因为他们希望通过为对象定义扩展方法来使用户能够使对象变为可等待,因此,即使您不拥有对象,您也可以使对象变为可等待。

签出这篇文章

于 2019-07-10T19:32:16.073 回答