Unity Coroutine 的内部原理

Unity 的 Coroutine 是运行在主线程中的,它以类似于时间片轮转的方式模拟了异步程序的运行状态,使用 Coroutine 可以使我们以串行的逻辑思维写出异步代码。

 IEnumerator coroutineB()
    {
        Debug.Log("coroutineB created");
        yield return new WaitForSeconds(2.5f);
        Debug.Log("coroutineB end");
    }

在上面的例子中,当时间片轮转coroutineB 运行时,它会执行 Debug.Log("coroutineB created");这句代码,然后执行 yield return new WaitForSeconds(2.5f);,但是这行 yield return 会把时间片使用权暂时交出去,知道等待时间达2.5秒时,时间片又回来继续运行下面的代码。
由于一个程序中会有很多个 Coroutine,所以时间片的使用权就在各个 Coroutine 中间进行不断的切换,由于 Coroutine 完全是运行在主线程这一个线程上的,所以,同一时刻必然只能有一个 Coroutine 可以获得时间片的使用权。
那么我们来考虑下,Unity Coroutine 的时间片是如何分配和调度的呢?也就是 Unity 内部的 CoroutineScheduler 的实现原理。
我猜测 Coroutine 的内部调度应该是基于 Update 的,应该有一个全局的 CoroutineScheduler 管理所有的 Coroutine, 然后这个 CoroutineScheduler 内部根据 Update 在每一帧进行调度判断和处理,决定这次的时间片分配给那个 Coroutine 来运行。当你设置了 Time.timeScale = 0 后,由于所有的 Update 都被冻结了,自然所有的 Coroutine 也会被冻结执行。
Unity Coroutine 的一个讨厌的地方是,你必须是 MonoBehaviour 对象才能使用 StartCoroutine 等函数,对于一般的 Class 是无法使用的。这也在一定程度上反证了 Coroutine 的运行原理是基于 MonoBehaviour 的事件调度的,比如 Update
More Effective Coroutines [FREE] 这个插件是一个很好的 Unity 自带 Coroutine 的替代者,可以在任何类里面运行,运行效率更高,GC 开销很小。非常赞的一点是,可以看到它的全部源代码,了解一个 Coroutine 是如何实现的。
我大概看了下它的源码,基本原理和我前面猜测的差不多,它的 Coroutine 也是基于 Update 实现的,但是不同之处在于,它同时还开放了基于 LateUpdate,FixedUpdate 和 SlowUpdate 的 Coroutine,功能更加的强大,非常推荐大家使用。