学习完深入理解C#关于await 和 async的一些总结:
1. await async 的用法
2. await 的一些限制
3.异步模式
4.可等待模式
5.从编译器的角度去理解
1. await async 的用法
async为修饰符用于在函数的返回值类型之前的任何位置,例如:
public static async Task Test();
亦或:
async public static Task Test();
两种方法皆可,不过团队开发中最好大家保持一致的写法。
await则在async方法中使用:
public static async Task Test(){
await Task.Delay(1);
}
2. await 的一些限制
使用 await 表达式也有一些约束条件。它不能在 catch 或 finally块、非异步匿名函数 、 lock 语句块或不安全代码中使用。
这些约束条件是为了保证安全,特别是关于锁的约束。
3.异步模式
C#编译器会对所有 await 都构建一个后续操作。这个理念表述起来非常简单,显然是为了可读性和开发者的健康。
基于任务的异步模式要稍有不同。它并不会将后续操作传递给异步操作,而是在异步操作开始时返回一个token,我们可以用这个token在稍后提供后续操作。它表示正在进行的操作,在返回调用代码前可能已经完成,也可能正在处理。token用于表达这样的想法:在这个操作完成之前,不能进行下一步处理。token的形式通常为 Task 或
Task<TResult> ,但这并不是必须的。
在C# 5中,异步方法的执行流通常遵守下列流程。
(1) 执行某些操作。
(2) 开始异步操作,并记住返回的token。
(3) 可能会执行其他操作。(在异步操作完成前,往往不能进行任何操作,此时忽略该步骤。)
(4) 等待异步操作完成(通过token)。
(5) 执行其他操作。
(6) 完成。
Task之类的都提供了用来等待的方法wait,不过这样该线程就处于一个等待的状态不能做任何其他的事情。这显然不是我们想要的,所以我们可以直接让它返回。然后异步地继续执行其他操作。如果想让调用者知道什么时候异步方法能够完成,就要传一个token回去,它们可以选择阻塞,或(更有可能)使用一个后续操作。通常,我们最终会得到一整栈互相调用
的异步方法,就好像为了一段代码进入了“异步模式”(async mode)。
4.可等待模式
异步方法在碰到await表达式之前都是使用同步的方式执行,我们来看看await到底完成了什么。
await表达式非常简单就是在其它表达式前面加入await,当然,对于能等待的东西是有限制的。
一般来说,我们只能等待(await)一个异步操作。换句话说,是包含以下含义的操作:
1. 告知是否已经完成;
2. 如未完成可附加后续操作;
3. 获取结果,该结果可能为返回值,但至少可以指明成功或失败。
所有的await表达式都需要提供上述的操作,你可能会认为它是一个接口其实不是,C#只提供了一个从 System.Runtime.CompilerServices 命名空间中的 INotifyCompletion接口
大量工作都是通过模式来表示的,为了方便描述很多都会实现类型于:
这样的类或接口,记住这不是C#提供的。有些类似于IEnumerbale 和 IEnumerator的机制。不过C#编译器要求awaiter必须实现 INotifyCompletion 。这主要是由于效率的原因。一些编译器的预发布版本根本就没有这个接口。编译器仅通过签名来检查所有其他成员。重要的是, GetAwaiter() 方法本身并不一定是一个标准的实例方法。它可以是 await 表达式中对象的扩展方法。IsCompleted 和 GetResult 必须是 GetAwaiter() 方法返回类型上的真正成员,但不一定具有公共属性,只要能由包含 await
表达式的代码访问即可。整个表达式本身也同样拥有一个有趣的类型:如果 GetResult() 返回 void ,那么整个 await 表达式就没有类型,而只是一个独立的语句。否则,其类型与 GetResult() 的返回类型相同。
5. 从编译器的角度去理解
async和await带来最大的好处就是,代码中使用异步不在那么麻烦能流式处理对应的业务逻辑。特别是对于在操作UI线程中使用异步带来的问题。async和await带来方便需要感谢c#编译器为我们生成了非常优秀的模板代码。下面我使用.net Reflector 工具对C#编译器生成的代码进行反编译查看。原始c#代码如下:
编译生成.exe文件,再利用.net Reflector反编译查看(主要查看的是异步方法)
感觉变化有点大。我们的关注点在
从名字看起来就知道是一个状态机了。
编译为我们生成了一个。可以看到其中参数作为该类的成员了
还有一个状态值用来表示当前执行的阶段。主要的实现方法为MoveNext。
这里我们看到了 和之前描述的一样再执行到await之前的所有方法都是在同步执行的。
后续操作就是对应的MoveNext,不过这里需要注意异步函数能够回到正确的线程中,是因为使用了 SynchronizationContext类。这个类简单的说就是一个线程通信的类,提供了Send(同步)和Post(异步线程池)方法来调用对应线程中的方法。这样整个异步的流程结合上面流程图基本可以还原一个过程。