协程的关键字是 IEnumerator
这是迭代器的意思,那么什么是迭代器?
先请看下面的代码:
定义一个Test类,实现IEnumerable接口:
class Test : IEnumerable<string>
{
public IEnumerator<string> GetEnumerator()
{
for (int i = 0; i < 5; i++)
{
yield return "test" + i;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
说一下 IEnumerable
和 IEnumerator
的关系:
- IEnumerable是一个接口,代表其是可枚举的。
- IEnumerator对象就是一个迭代器(iterator:MoveNext(),Reset(),Current)。
用上面的类,写一个测试程序:
Test test = new Test();
foreach (string s in test)
{
Console.WriteLine(s);
}
此时输出结果为:
可以猜到,输出的就是 yield return xxx;
后面的xxx。
那么换一种写法试试:
Test test = new Test();
var e = test.GetEnumerator();
while (e.MoveNext())
{
Console.WriteLine(e.Current);
}
这种写法就是基于迭代器(iterator)的方式。先来看一下迭代器的定义:
public interface IEnumerator
{
bool MoveNext();
void Reset();
Object Current { get; }
}
从定义可以理解,使用foreach这样的枚举操作的时候,枚举数被定为在集合的第一个元素前面。
使用迭代器在执行迭代,首先会执行 MoveNext(), 如果返回true,说明下一个位置有对象,然后此时将Current设置为下一个对象,这时候的Current就指向了下一个对象;如果返回false,说明下一个位置没有对象,此时结束遍历。
虽然看起来和遍历数组一样,但本质上是把其包装成了一个迭代器执行,每次执行到yield的时候,程序就把需要的东西传递出去,再停住,不会继续往下执行。
由于这里yield下面没有什么其他语句,所以就会依旧 i++ ,继续执行下去,然后又返回一个值。
那么回到Unity3d中,协程的用法如下:
void Start()
{
StartCoroutine(DoSomething());
}
IEnumerator DoSomething()
{
DoBefore();
yield return new WaitForSeconds(1f);
DoAfter();
}
这里Unity3d的运行效果,在 Start() 方法开启协程后,程序会执行 DoBefore() 函数,直到遇见第一个yield,就会暂时把协程挂起,直到下一帧的Update渲染完成之后再获取到迭代器当前的 Current 对象,此时就是 WaitForSeconds 这个对象。
通过此对象,判断是否还需要进行等待,再进行 DoAfter() 的回调。
我们可以写伪代码来表述内部的逻辑:
void StartCoroutine(IEnumerator e)
{
if (!e.MoveNext())
{
return;
}
var obj = routine.Current;
if (obj is WaitForSeconds)
{
//判断是否到达时间 如果超过了设置时间 就继续回调
}
}
在注释处,会利用现有的定时器机制,注册定时器,并继续进行 DoAfter() 的回调。