协程

最近在看面试题,经常会遇到一个经典的八股问题:协程是多线程吗?底层原理是什么。虽然接触Unity好几年了,但是一直没用探究过其中的底层原理,看到这个问题也大脑空白。今天就来探寻一下其中的原理。

测试代码

废话不多说,直接上代码。代码非常简单一个TestCoroutine函数内yield return null和log输出。
CoroutineCode

IL代码

C#的代码看不出什么,打开IL代码。
CoroutineILCode
CoroutineILCode
可以清晰编译器为我们的协程生成了一个类名叫d__1,并且继承了IEnumerator,类中除了IEnumerator的Current、MoveNext()和Reset(),还额外多了一个state变量。我们自己写的TestCoroutine函数中就new了这个类的实例。其中类中最关键的就是这个state变量的使用。

state变量的使用

CoroutineILCode
从截图可以看到,MoveNext()中维护的switch充当了一个类似状态机的逻辑,每个yield return null就变成了一个case,state变量就变成了case块的记录,通过枚举去执行不同的逻辑,这就是协程可重入机制的根本原因。

协程的性能消耗

既然协程是new的了一个类,那就一定在堆上分配了内存,并且会触发GC。通过Unity的profiler可以看到,代码触发一个40B的GC。
CoroutineCost
问了一下AI,这40B主要来自于(我的系统是64位的),类的对象头16个字节,int类型的state变量4字节,object引用8字节,this指针8字节,最后再有4字节的内存对齐,所以总计=16+4+8+8+4=40b。
CoroutineDetailCost

优化/替代方案

既然协程有GC消耗所以我们应避免使用协程,在业务逻辑中使用协程只有一种情形就是等待。1、等待固定时间。2、等待固定帧。而这两种情形都可以通过在Update中通过轮询去替代。