本文太乱,推荐frankjfwang的:全面解析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,语言本身就支持协程。协程也被叫做轻量级线程。

通俗点讲就是定义一大堆任务。然后通过一个线程轮着对每一个任务都执行一下,协作执行。它的厉害之处在于每执行到一个任务的时候。它都能够从这个任务上一次中断的地方開始执行。

在我们一般的印象中,仅仅有操作系统对线程进行调度的时候才会干这种事情,进行各种进栈,保存状态。

而协程,总共也仅仅是执行在一个线程中。要是使用线程本身的系统栈,早就暴了。因此在这里,实现的时候是用内存来模拟栈的操作。详细实现,我想复杂度一定会不小。

我们知道,线程比进程轻量级,因此产生一个线程消耗的资源比进程少,上下文切换也比进程节约。而协程比线程更加轻量级。上下文切换更是迅速。

于是在server编程方面给人无限想象。虽然眼下还没有出现一款主流的採用协程的webserver。可是Go语言开发的web服务的性能已经崭露头角了。

二、Unity的Coroutine运行现象

第一种方法:

voidStart()

{

print("Starting " +Time.time);----------------------------------------1

StartCoroutine(WaitAndPrint(2));-------------------------------------2

print("Done " +Time.time);-------------------------------------------3

}

IEnumerator WaitAndPrint(float waitTime)

{

yield return new WaitForSeconds(waitTime);------------------------4

print("WaitAndPrint " + Time.time);----------------------------------5

}

该段代码的运行顺序是12435

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

IEnumerator Start()

{

print("Starting " +Time.time);----------------------------------------1

yield return StartCoroutine(WaitAndPrint(2.0F));------------------------2

print("Done " +Time.time);------------------------------------------3

}

IEnumerator WaitAndPrint(float waitTime)

{

yield return new WaitForSeconds(waitTime);----------------------------4

print("WaitAndPrint " + Time.time);-----------------------------------------5

}

该段代码的运行顺序是12453

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

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

三、Unity官方文档对coroutine的解释

Normal coroutine updates are run after theUpdate function returns. Acoroutine is a function that can suspend its execution (yield) until the givenYieldInstruction finishes. Different
uses of Coroutines:

yield; The coroutine will continue after all Update functionshave been calledon the next frame.

yield WaitForSeconds(2); Continueafter a specified time delay, after all Update functions have been called for
theframe.

yield WaitForFixedUpdate(); Continue afterall FixedUpdate has been called on all scripts.

yield WWWContinue aftera WWW download has completed

yield StartCoroutine(MyFunc);
Chains the coroutine, and will wait for the MyFunc coroutine to completefirst.

C#要在yield coroutine之间加上returnkeyword。

四、Unity中的Coroutine原理推測

     虚拟机分段运行机制。 同类型嵌套用栈存放实现串行运行:.NET虚拟机在每一帧循环中, 会依次进入每一个编译器提前定义好的入口。对于Coroutine类型,编译器须要产生一些代码。在Coroutine类型指定的时间或事件完毕后(.net的虚拟机用函数指针进行标记管理现场和在流程中每帧检查时间或者事件满足后发送消息,将cpu全部权交给yield中断的现场。或是通过包括不同Coroutine迭代器的多个管理类管理各个coroutine,
每帧用coroutine子类通过多态检查时间或事件到达。将cpu全部权交给coroutine子类中断的现场)。从yield中断后的代码处继续往下运行, 这样就形成了我们看到的一个function能分段运行的机制。

而对于嵌套Coroutine类型,会串行的运行而不是并行的,可能.net虚拟机对于同coroutine类型用栈存放,栈顶的先运行,从而实现串行运行,假设外层的不使用yield return,那么不会串行运行,而是并行运行。

于是就能够解释上面样例中的运行次序问题。

原理图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQmx1ZXMxMDIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

见:http://www.richardfine.co.uk/2012/10/unity3d-monobehaviour-lifecycle/

事实上yield WaitForSeconds/null/WaitForEndOfFrame都是通过整个游戏While循环来驱动的,Yield仅仅是交出控制权给外部主线程且Coroutine设置事件挂起Coroutine协程(不是线程仅仅是分段运行机制)。当事件发生时候(不用下一帧到达。可是要在固定时间段内Update前或者后或Render后,超过了就会到下一帧)则进入协程里面的代码。

五、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);

5.协程函数返回值和參数类型。组合的设计模式:

协同程序的返回类型为Coroutine类型。在Unity3D中,Coroutine类继承于YieldInstruction,所以,协同程序的返回类型仅仅能为null、等待的帧数(frame)以及等待的时间。

协同程序的參数不能指定ref、out參数。可是,我们在使用WWW类时会常常使用到协同程序。因为在协同程序中不能传递參数地址(引用),也不能输出对象。

这使得每下载一个WWW对象都得重写一个协同程序。解决问题的方法是建立一个基于WWW的类(用组合模式来解决-事实上就是不通过函数传參全局关联一个对象了),并实现一个下载方法。例如以下:

using UnityEngine;

using System.Collections;

public class WWWObject : MonoBehaviour

{

public WWW www;

public WWWObject(string url)

{

if(GameVar.wwwCache)

www = WWW.LoadFromCacheOrDownload(url, GameVar.version);

else

www = new WWW(url);

}

public IEnumerator Load()

{

Debug.Log("Start loading : " + www.url);

while(!www.isDone)

{

if(GameVar.gameState == GameState.Jumping || GameVar.gameState ==GameState.JumpingAsync)

LoadScene.progress = www.progress;

yield return 1;

}

if(www.error != null)

Debug.LogError("Loading error : " + www.url + "\n" +www.error);

else

Debug.Log("End loading : " + www.url);

}

public IEnumerator LoadWithTip(string resourcesName)

{

Debug.Log("Start loading : " + www.url);

LoadScene.tipStr = "Downloading  resources<" + resourcesName + "> . . .";

while(!www.isDone)

{

if(GameVar.gameState == GameState.Jumping || GameVar.gameState ==GameState.JumpingAsync)

LoadScene.progress= www.progress;

yield return 1;

}

if(www.error != null)

Debug.LogError("Loading error : " + www.url + "\n" +www.error);

else

Debug.Log("End loading : " + www.url);

}

}

调用:

using UnityEngine;

using System.Collections;

using System.Collections.Generic;

public class LoadResources : MonoBehaviour

{

static string url ="http://61.149.211.88/Package/test.unity3d";

public static WWW www = null;

IEnumerator Start()

{

if(!GameVar.resourcesLoaded)

{

GameVar.gameState = GameState.Jumping;

WWWObject obj = new WWWObject(url);

www = obj.www;

yield return StartCoroutine(obj.LoadWithTip("Textures"));

GameVar.resourcesLoaded = true;

GameVar.gameState = GameState.Run;

}

}

}

參考文章:

http://game.ceeger.com/forum/read.php?tid=13148

http://www.zhihu.com/question/23895384

http://blog.csdn.net/tkokof1/article/details/11842673

http://blog.csdn.net/tkokof1/article/details/12834939

http://www.richardfine.co.uk/2012/10/unity3d-monobehaviour-lifecycle/

http://dsqiu.iteye.com/blog/2029701

Unity3D中的Coroutine具体解释的更多相关文章

  1. Unity3D中的Coroutine详解

    Unity中的coroutine是通过yield expression;来实现的.官方脚本中到处会看到这样的代码. 疑问: yield是什么? Coroutine是什么? unity的coroutin ...

  2. Unity3D中的Coroutine及其使用(延时、定时调用函数)

    http://blog.csdn.net/nizihabi/article/details/47606887 一.Coroutine(协程)的概念和本质 在网上的一些资料当中,一直将Coroutine ...

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

    Unity中的coroutine是通过yield expression;来实现的.官方脚本中到处会看到这样的代码. 疑问: yield是什么? Coroutine是什么? unity的coroutin ...

  4. 【吐血推荐】简要分析unity3d中剪不断理还乱的yield

    在学习unity3d的时候很容易看到下面这个例子: void Start () { StartCoroutine(Destroy()); } IEnumerator Destroy(){ yield ...

  5. 【Unity3D】简要分析unity3d中剪不断理还乱的yield

    在学习unity3d的时候很容易看到下面这个例子: void Start () { StartCoroutine(Destroy()); } IEnumerator Destroy(){ yield ...

  6. 【转】简要分析unity3d中剪不断理还乱的yield

    在学习unity3d的时候很容易看到下面这个例子: 1 void Start () { 2 StartCoroutine(Destroy()); 3 } 4 5 IEnumerator Destroy ...

  7. 简要分析unity3d中剪不断理还乱的yield

    在学习unity3d的时候非常easy看到以下这个样例: void Start () { StartCoroutine(Destroy()); } IEnumerator Destroy(){ yie ...

  8. Unity3D中UGUI不使用DOTween制作渐隐渐现效果

    在做UI后期设计时,我们可能要对UI做一些特效,这篇文章我们来学习下如何在Unity3d中对实现渐隐渐现的效果, 首先我们看下Unity New UI即UGUI中渐隐渐现的做法. 观察我们会发现Uni ...

  9. 【Unity3d游戏开发】Unity3D中的3D数学基础---向量

    向量是2D.3D数学研究的标准工具,在3D游戏中向量是基础.因此掌握好向量的一些基本概念以及属性和常用运算方法就显得尤为重要.在本篇博客中,马三就来和大家一起回顾和学习一下Unity3D中那些常用的3 ...

随机推荐

  1. python抢票开发——设备预约助手实现

    女朋友是药学院的,做实验时需要在特定的网站上进行设备预约,由于预约人数过多,从而导致从浏览器登录不进去或者登录进去预约失败等情况,所以我用python帮她写了一个抢位助手,让程序自动去进行位置预定,实 ...

  2. ACM_三角形蛇形矩阵

    三角形蛇形矩阵 Time Limit: 2000/1000ms (Java/Others) Problem Description: 小铠觉得各类题型是要温故而知新的,所以他叫小发出一道类似做过的题. ...

  3. 题解报告:hdu 1142 A Walk Through the Forest

    题目链接:acm.hdu.edu.cn/showproblem.php?pid=1142 Problem Description Jimmy experiences a lot of stress a ...

  4. 从源码看ASP.NET框架(一)-打造页面控件树

    测试实例如下: 前台代码MyFirstWeb.aspx(没有服务器控件,即没有runat) CodeBehind="MyFirstWeb.aspx.cs":表示代码后置类文件 In ...

  5. 话说:Hibernate二级缓存

    Hibernate缓存分类: 一.Session缓存(又称作事务缓存):Hibernate内置的,不能卸除. 缓存范围:缓存只能被当前Session对象访问.缓存的生命周期依赖于Session的生命周 ...

  6. mysql外键创建失败原因

    引用:http://blog.csdn.net/wangpeng047/article/details/19624351 首先,如果和外键相关的几张表中已经插入了数据,可能导致外键插入的失败 在MyS ...

  7. Android基础TOP3_1:纵横屏切换

    在Res下建立layout-port文件夹  为竖屏时加载的界面: 建立layout-land 文件夹 为横屏加载的界面

  8. Rsync 传输不需要输入密码

    1.背景 1)        一个作为服务器端:VM3(IP: 3.9.8.151) 2)        一个作为客户端:VM2(IP: 3.9.8.157) 3)        服务器端和客户端网络 ...

  9. 开源业务规则引擎JBoss Drools

    Drools 是什么? 规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策.接受数据输入,解释业务规则,并根据业务规 ...

  10. 使用码云gitee.com托管代码

    1.新建项目 可以看到团队资源管理器是这样的,已经在本地有存储库,所有更改可以保存本地 2.在码云上新建项目 项目名称必填,其它项根据情况填写 3.复制项目地址关联到本地存储库 填写码云的项目地址,发 ...