在Unity中我经常用到Coroutine的功能,但是对于Coroutine一直有一些疑问没有得到答案,下面先上一个在项目里面经常使用Coroutine的场景的sample:
1 |
|
-
Test中yield语句调用之后为什么可以立即停止,等到 WaitForSeconds 的时候接着执行,这是怎么做到的?
-
Test的返回值是IEnumerator,但是函数内部的return用了yield来修饰,yield到底做了什么工作。
针对第二个问题我找到了一个比较详细的解释, 里面有这样一段话:
The yield keyword actually does quite a lot here. The function returns an object that implements the IEnumerable interface.
相当于yield关键字会自动生成一个集成IEnumerable的对象,那么这个对象到底是什么样子呢? 首先使用ILSpy工具查看下这段代码代码的C#版本:
1 |
|
可以看到通过ILSpy解析之后Test方法已经完全变了,但是也验证了上面的说法。yield关键字会自动转换成一个IEnumerable的对象,这里自动生成了一个名字为TestCoroutine.{Test}c_Iterator0 的类,并且创建了这个类型的 {Test}c_Iterator 对象。
1 |
|
那这个对象内部是怎样实现的呢?现在我们得看下TestCoroutine.{Test}c_Iterator0的实现了(这里方法自动加了DebuggerHidden属性,所以只能看IL版本的代码)
1 |
|
这里只截取了MoveNext方法,因为这个方法里面包含了我们Sample中的Test函数的所有逻辑操作。这段代码我加了注释,通过看代码基本上已经解决了我的第一个疑惑了,下面再做一下分析。Sample中的方法Test在编译之后会转换成大概这个样子(只是模拟):
1 |
|
到这里已经知道了yield关键字所做的操作了,那么现在来看看这个{Test}c_Iterator0对象是怎样被调用的。
首先我们看到我们的Sample中的代码:
1 |
|
这说明创建的{Test}c_Iterator0对象传入的StartCoroutine方法中了,浏览了StartCoroutine的实现,这个方法直接绑定的C++层MonoBehaviour的StartCoroutine方法,这里简述下里面的实现(不贴Unity代码了):
- 调用StartCoroutine方法,该方法会以传入的IEnumerator参数(这里是{Test}c_Iterator0对象)创建一个C++的Coroutine对象
- 这个对象保存会保存参数IEnumerator对象,并且会先获取出IEnuerator的MoveNext和Current方法。这两个方法也是IEunerator最关键的方法
- 创建好之后这个Coroutine对象会保存在MonoBehaviour一个成员变量List中,这样使得MonoBehaviour具备StopCoroutine功能,StopCoroutine能够找到对应Coroutine并停止
- Coroutine对象会调用成员方法run,启动这个Coroutine
这个步骤大概是这样的(代码只表现大概逻辑,不能正确执行):
1 |
|
那么现在生成了C++层的Coroutine对象了,再分析Coroutine现在作为执行者它是怎么实现的。Coroutine里面针对yield对象(Sample里面是WaitForSeconds)类型做了处理:
- WaitForSeconds
- WaitForFixedUpdate
- WaitForEndOfFrame
- Coroutine (C#层)
- WWW
- AsyncOperation
这些类型处理的方式是定义了一个类似定时调用的管理类DelayedCallManager。比如我的条件是WaitForSceonds(5), 那么Coroutine里面会创建一个CallDelayed,把时间设置魏5秒,然后DelayedCallManager的Update里面会直接算时间,到时间了就会回调Coroutine。WWW类型比较特殊它本身做了类似的处理,它提供了一个方法CallWhenDone,当它完成的时候直接回调Coroutine。
这个步骤大概是这样的(代码只表现大概逻辑,不能正确执行):
1 |
|
其他的的类型会直接在下一帧调用,比如yield return 0
整个过程粗略的看大概就是这个样子。上面的分析没有深入到每个条件判断之类的,但是已经够了解Coroutine的全貌了。有错误的地方欢迎指出来,非常感谢。