1. 什么是协程 #
A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done. → 协程是一个部分执行, 遇到条件 (yield return
) 会挂起, 直到条件满足才会被唤醒继续执行后面代码的一种函数.
2. 为什么要有协程 #
协程的作用一共有两点 :
1. 延时等待一段时间执行代码
2. 等某个操作完成之后再执行后面的代码
总而言之, 协程控制了代码在特定的时机执行, 是对单线程控制权的交替.
3. 协程的原理 #
Coroutines 不是多线程, 不是异步技术, 协程都在 MainThread 中执行, 而且每个时刻只有一个 Coroutine 在执行. Coroutine 是一个 function, 可以部分执行, 当条件满足时, 未来会被再次执行直到整个函数执行完毕.
协程能够把一个计算或操作,分解成若干步,并且可以在任何一步停下来,并在需要的时候继续执行剩下的步骤。这样的模型给予了更细粒度的控制一个操作或是功能, 比如, 一个非常耗时间的操作, 被分步执行可以更好的控制程序响应. 比如, 一个操作需要依赖各种条件, 可以更好的处理条件不满足的时候的情况. 也能够更好的把操作或是计算过程中的状态变化, 与其他的状态变化交互, 然而, 程序运行的过程就是抽象数据结构和结构不断变化的过程, 协程能够优雅自然的进行这个变化过程的需求.
Unity 在每一帧都会去处理 GameObject 里带有的 Coroutine Function, 直到 Coroutine Function 被执行完毕. 当一个 Coroutine 开始启动时, 它会执行到遇到 yield 为止, 遇到 yield 的时候 Coroutine 会暂停执行, 直到满足 yield 语句的条件, 会开始执行 yield 语句后面的内容, 直到遇到下一个 yield 为止 ... 如此循环直到整个函数结束, 这就是可以将一个函数分割到多个帧里去执行的思想.
也就是说, Coroutines 最棒的就是函数的执行可以不在一次 Frame 里完成, 可以在多个 Frame 中完成. 比如, 我们希望看到物体透明度的改变, 如果让 color.a 的变化在一帧内完成, 那么我们是看不出来这其中的变化得, 因为太快, 所以让 color.a 的变化必须在多个帧内完成, 我们才有可能用肉眼看到这一变化的过程.
Unity 的协程系统基于 C# 的接口 : IEnumerator, 允许为自己的集合类型编写枚举类.
4. 协程与线程的区别 #
历史上先有的协程, 是操作系统用来模拟多任务并发, 协程是非抢占式的, 多任务时间片不能公平分享, 线程是抢占式的;
线程能利用多核达到真正的并行计算, 如果任务设计的好, 线程能几乎成倍的提高你的计算能力, 但是线程的缺点也很明显, 那就是没有设计好导致大量的锁 | 切换 | 等待, 这些很多都是应用层的问题. 而协程因为是非抢占式, 所以需要用户自己释放使用权来切换到其他协程, 因此同一时间其实只有一个协程拥有运行权, 相当于单线程的能力.
协程相对线程的最大优点就是 : 让原来要使用异步 + 回调方式写的非人类代码, 可以用看似同步的方式写出来.
5. Unity 中协程相关的类 #
- yield return null; → 等待下一帧中的 Update 函数执行完之后再继续执行;
- yield return new WaitForSeconds(10); → 延迟 10 秒后再继续执行;
- yield return new WaitForFixedUpdate(); → 等待所有脚本中的 FixedUpdate 函数结束之后再继续执行;
- yield return new WaitForEndOfFrame(); → 等待该帧中所有 Camera 和 GUI 对象渲染完毕, 在帧被显示到屏幕之前恢复执行前面的代码;
- yield return new WWW(url); → 等待 url 下载完后再继续执行;
- yield return StartCoroutine(MyFunc()); → 等待协程结束后再执行;
6. 需要注意的几点 #
- 协程是 C# 线程的替代品, 是 Unity 不使用线程的解决方案. 但是, 协程不是线程, 不能进行异步执行, 协程和 MonoBehaviour 的 Update 函数一样也是在 MainThread 中执行的.