我们已经知道 Task
是可等待的,但是去看看 Task
类的实现,几乎找不到哪个基类、接口或者方法属性能够告诉我们与 await
相关。
而本文将探索什么样的类是可使用 await 异步等待的?
Dixin’s Blog - Understanding C# async / await (2) The Awaitable-Awaiter Pattern 一文解决了我们的疑惑。async
/await
是给编译器用的,只要我们的类包含一个 GetAwaiter
方法,并返回合适的对象,我们就能让这个类的实例被 await
使用了。
既然需要一个 GetAwaiter
方法,那我们先随便写个方法探索一下:
1
2
3
4
5
6
7
8
9
10
Test DoAsync()
{
return new Test();
}
class Test
{
void GetAwaiter()
{
}
}
尝试调用:
1
await DoAsync();
编译器告诉我们:
Test.GetAwaiter() 不可访问,因为它具有一定的保护级别。
原来 GetAwaiter 方法需要是可以被调用方访问到的才行。
于是我们将 GetAwaiter
前面的访问修饰符改成 public
。现在提示变成了:
await 要求类型 Test 包含适当的 GetAwaiter 方法。
考虑到一定要获取到某个对象才可能有用,于是我们返回一个 Test2 对象:
1
2
3
4
5
6
7
8
9
10
11
public class Test
{
public Test2 GetAwaiter()
{
return new Test2();
}
}
public class Test2
{
}
这时编译器又告诉我们:
Test2 未包含 IsCompleted 的定义。
加上 public bool IsCompleted { get; }
,编译器又说:
Test2 不实现 INotifyCompletion。
于是我们实现之,编译器又告诉我们:
Test2 未包含 GetResult 的定义。
于是我们加上一个空的 GetResult
方法,现在编译器终于不报错了。
现在我们一开始的 DoAsync
和辅助类型变成了这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 注:此处为试验代码。
private Test DoAsync()
{
return new Test();
}
public class Test
{
public Test2 GetAwaiter()
{
return new Test2();
}
}
public class Test2 : INotifyCompletion
{
public bool IsCompleted { get; }
public void GetResult() { }
public void OnCompleted(Action continuation) { }
}
总结起来,要想使一个方法可被 await
等待,必须具备以下条件:
- 这个方法返回一个类 A 的实例,这个类 A 必须满足后面的条件。
- 此类 A 有一个可被访问到的
GetAwaiter
方法(扩展方法也行,这算是黑科技吗?),方法返回类 B 的实例,这个类 B 必须满足后面的条件; - 此类 B 实现
INotifyCompletion
接口,且拥有bool IsCompleted { get; }
属性、GetResult()
方法、void OnCompleted(Action continuation)
方法。
更多编写自定义 Awaiter 的文章可以阅读:
入门篇:
- .NET 中什么样的类是可使用 await 异步等待的?
- 定义一组抽象的 Awaiter 的实现接口,你下次写自己的 await 可等待对象时将更加方便
- .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
实战篇:
参考资料
本文会经常更新,请阅读原文: https://blog.walterlv.com/post/what-is-an-awaiter.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://blog.walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 ([email protected]) 。