首页 > 代码库 > 【Unity3D/C#】Unity3D中的Coroutine详解

【Unity3D/C#】Unity3D中的Coroutine详解

Unity中的coroutine是通过yield expression;来实现的。官方脚本中到处会看到这样的代码。

疑问:

yield是什么?

Coroutine是什么?

unity的coroutine程序执行流程怎么那么奇怪?

unity中的coroutine原理是什么,怎么实现的?

使用unity的coroutine需要注意什么问题?

 

一、yield的在几种语言中的程序执行特性

     Lua中的yield是使得协同函数运行->挂起并且传递参数给resume。resume使得协同函数挂起->运行并且传递参数给协同函数。

     C#中yield return/break是用于函数查询集合生成器里面的值(类似迭代)返回,并记录当前现场,下次查询时从上一次记录的yield现场处,继续往下执行,直到继续往下执行没有了,那么退出这段yield的逻辑。yield break会终止掉yield迭代逻辑并跳出。
YieldImplementation:
   1).Caller callsfunction
   2).Caller requestsitem 按需请求一个元素
   3).Next itemreturned 返回请求的元素
   4).Goto step #2

    Python中的yield expression, 有yield的函数就变成了一个生成器,调用该函数返回的是迭代器对象,用迭代器对象调用next方法(或者循环中会自动调用next方法),才开始执行函数,执行到yield expression处则中断,返回迭代器当前的值,并保留现场,下次调用next则从现场处开始执行,迭代完了就停止了。可以看出Python中的yield和C#中的yield是类似的,用于创建生成器,执行时中断返回迭代器值,并记录现场,下次从现场处继续执行。

   Unity中的yield就是和C#,python中的类似,因为unity是基于.net框架的,且unity脚本开始是用Boo(Python的一个变种)写的。只是unity中多了coroutine特性类型,和StartCoroutine的coroutine管理类。StartCoroutine不是启动了一个新的线程,而是开启一个协同程序,默认unity所有代码都在一个线程中(http://answers.unity3d.com/questions/280597/new-thread-vs-startcoroutine.html)。

coroutine语言层面的原理:

 

在两年前,协程似乎是一个很高级的东西,随后大多数语言或多或少都支持协程。我比较熟悉的有Python的gevent,Lua的coroutine,Go的goroutine。尤其是Lua和Go,语言本身就支持协程。协程也被叫做轻量级线程。通俗点讲就是定义一大堆任务,然后通过一个线程轮着对每个任务都执行一下,协作运行。它的厉害之处在于每运行到一个任务的时候,它都可以从这个任务上一次中断的地方开始运行。在我们一般的印象中,只有操作系统对线程进行调度的时候才会干这样的事情,进行各种进栈,保存状态。而协程,总共也只是运行在一个线程中,要是使用线程本身的系统栈,早就暴了。因此在这里,实现的时候是用内存来模拟栈的操作。具体实现,我想复杂度一定会不小。

我们知道,线程比进程轻量级,因此产生一个线程消耗的资源比进程少,上下文切换也比进程节约。而协程比线程更加轻量级,上下文切换更是迅速。于是在服务器编程方面给人无限想象。尽管目前还没有出现一款主流的采用协程的web服务器。但是Go语言开发的web服务的性能已经崭露头角了。

 

 

二、Unity的Coroutine执行现象

第一种方法:  

voidStart()    {       print("Starting " +Time.time);       StartCoroutine(WaitAndPrint(2));       print("Done " +Time.time);    }   IEnumerator WaitAndPrint(float waitTime)    {       yield return new WaitForSeconds(waitTime);       print("WaitAndPrint " + Time.time);    }

该段代码的执行顺序是12435 

执行到4协程注册了事件,控制权交给外部线程;外部线程执行到3;事件发生,程序分段执行机制goto到协程处记录了堆栈信息执行5语句。

 // Use this for initialization    void Start()    {        StartCoroutine(IEnumeratorStart());    }    // Update is called once per frame    void Update()    {    }    IEnumerator IEnumeratorStart()    {       print("Starting " + Time.time);        yield return StartCoroutine(WaitAndPrint(2.0F));       print("Done " + Time.time);    }    IEnumerator WaitAndPrint(float waitTime)    {       yield return new WaitForSeconds(waitTime);        print("WaitAndPrint " + Time.time);     }

该段代码的执行顺序是12453

Why?这么奇怪的执行方式。

程序执行到4,执行yield return表达式注册事件交出控制权给外部,因为外部还要交出控制权也需要执行yield return后面的表达式语句因此会重入WaitAndPrint函数接着协程当前状态下一步执行所以执行到5,yield return 后面表达式语句执行完毕控制权完全交出,之后才执行3,根本原因是yield return 不能直接嵌套后面需要跟一个表达式(事件)。

五、Unity中使用Coroutine需要注意的问题:

1.使用的地方和不能使用的地方:

必须在MonoBehaviour或继承于MonoBehaviour的类中调用 yield coroutine。yield不可以在Update或者FixedUpdate里使用。

 

2.开启协程:

StartCoroutine(string methodName)和StartCoroutine(IEnumeratorroutine)都可以开启一个协程,

区别:

使用字符串作为参数时,开启协程时最多只能传递一个参数,并且性能消耗会更大一点; 而使用IEnumerator 作为参数则没有这个限制。

 

3.删除协程:

1).在Unity3D中,使用StopCoroutine(stringmethodName)来终止该MonoBehaviour指定方法名的一个协同程序,使用StopAllCoroutines()来终止所有该MonoBehaviour可以终止的协同程序。

包括StartCoroutine(IEnumerator routine)的。

2).还有一种方法可以终止协同程序,即将协同程序所在gameobject的active属性设置为false,当再次设置active为ture时,协同程序并不会再开启;

如是将协同程序所在脚本的enabled设置为false则不会生效。

 

4.js和C#中使用区别:

在C#中要使用 yield return而不是yield。

C#中yield(中断)语句必须要在IEnumerator类型里,C#方法的返回类型为IEnumerator,返回值如(eg:yield return new WaitForSeconds(2); 或者 yield returnnull);

 

【Unity3D/C#】Unity3D中的Coroutine详解