深入探讨Unity协程及其使用
协程
协程在Unity中是个很重要的东东,相信很多人都使用过,它能够非常方便的进行异步等待操作,可以说,用好协程,可以使你的代码更加优雅,然而,如果用不好,代码就会有变成翔的可能,甚至已经变成了翔,你自己居然还不知道。那么,我们就来好好聊聊协程吧。
协程的本质
协程并不是Unity创造的,它本来就存在于C#。协程的本质是迭代器,通过调用MoveNext()
,直到迭代结束。
通过下面的代码,可以清晰的看到他的工作原理:
static IEnumerator<int> MyCoroutine()
{
yield return 0;
yield return 1;
for( int i = 2; i < 10; ++ i )
yield return i;
}
static void Main(string[] args)
{
var mc = MyCoroutine();
while( mc.MoveNext())
{
Console.WriteLine( mc.Current );
}
Console.ReadKey();
}
在Unity的游戏循环中,每次循环或者说每帧都会去调用MoveNext()
,于是看起来,协程“并行”了。然而,协程并不是线程,它本质并不是并发的。
协程带来的问题
请看下面的例子:
public float speed = 0.5f / 1f;
CanvasGroup group;
private void Start()
{
group = GetComponenet<CanvasGroup>();
}
public void Show()
{
StartCoroutine(_show());
}
public void Hide()
{
StartCoroutine(_hide());
}
private IEnumerator _show()
{
while( group.Alpha < 0.999 )
group.Alpha += speed * Time.deltaTime;
group.Alpha = 1;
}
private IEnumerator _hide()
{
while( group.Alpha > 0.001 )
group.Alpha -= speed * Time.deltaTime;
group.Alpha = 0;
}
上面的例子中,实现了一个UI的渐隐和渐现功能,一般情况下,它能很好的工作。当你调用了Show()
,他会渐现,当你调用了Hide()
,他会渐隐。然而,它潜在的问题是,如果你调用了Show()
,渐现到一半时(_show
协程未结束),接着调用Hide()
,那么两个协程将同时运行,一个负责增加group.Alpha
,另一个负责减少group.Alpha
,导致group.Alpha
始终不会高于0.999,也始终不会低于0.001,界面就卡在那里了。
那么为了保证每次只有一个协程在运行,你可能会这么改:
public void Show()
{
StopAllCoroutine();
StartCoroutine(_show());
}
public void Hide()
{
StopAllCoroutine();
StartCoroutine(_hide());
}
虽然通过加一个StopAllCoroutine()
能解决上面的问题,但最起码,它已经不怎么“优雅”了。更优雅的实现同样功能,应该考虑使用状态机,而不是协程。
再来看一个例子:
这是unity官方在B站发布的视频,他的大意是:第一,协程是语法糖,他本质上是一个状态机,加上一堆goto
语句。第二,也是最重要的,不要让一个协程永远不退出,就是不要有上图例子中的while(true)
这样的协程,他强烈不建议这么用,因为这样可能会有内存问题。有兴趣的可以去看看原视频——“豆知识 匿名类”。
到底应该什么时候用协程
使用协程应该满足“FIRE AND FORGET”原则,即“点火后忘记”原则。就是说,只需要发起它,而不用管它后续的情况(不需要时刻记得它),就可以使用协程。比如说,发起一个资源下载,然后等他出来结果。
另外,协程只工作在主线程,他只是循环的被调用,本身并不能解决并发的问题,所以,如果你想要并发,请使用多线程。
白猫,黑猫,抓到老鼠就是好猫,总之,没有绝对的好于坏,具体需求要具体分析,协程也好,线程也罢,各有各的优缺点,只要能实现功能需求,达到性能要求,就是好的,但这就需要我们深入的去理解功能点,分析需求逻辑。