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);----------------------------------------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
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?这么奇怪的执行方式。
三、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之间加上return关键字。
四、Unity中的Coroutine原理猜测:
虚拟机分段执行机制, 同类型嵌套用栈存放实现串行执行:.NET虚拟机在每一帧循环中, 会依次进入每个编译器预定义好的入口。对于Coroutine类型,编译器需要产生一些代码,在Coroutine类型指定的时间或事件完成后(.net的虚拟机用函数指针进行标记管理现场和在流程中每帧检查时间或者事件满足后发送消息,将cpu所有权交给yield中断的现场,或是通过包含不同Coroutine迭代器的多个管理类管理各个coroutine, 每帧用coroutine子类通过多态检查时间或事件到达,将cpu所有权交给coroutine子类中断的现场),从yield中断后的代码处继续往下执行, 这样就形成了我们看到的一个function能分段执行的机制。
而对于嵌套Coroutine类型,会串行的执行而不是并行的,可能.net虚拟机对于同coroutine类型用栈存放,栈顶的先执行,从而实现串行执行,如果外层的不使用yield return,那么不会串行执行,而是并行执行。于是就可以解释上面例子中的执行次序问题。
原理图:

见:http://www.richardfine.co.uk/2012/10/unity3d-monobehaviour-lifecycle/
五、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详解的更多相关文章
- 【Unity3D/C#】Unity3D中的Coroutine详解
Unity中的coroutine是通过yield expression;来实现的.官方脚本中到处会看到这样的代码. 疑问: yield是什么? Coroutine是什么? unity的coroutin ...
- Tornado中gen.coroutine详解
1.gen.coroutine的作用 自动执行生成器 2.Future对象 在介绍异步使用之前,先了解一下Future对象的作用. Future简单可以理解为一个占位符,将来会执行的对象,类似java ...
- php中关于引用(&)详解
php中关于引用(&)详解 php的引用(就是在变量或者函数.对象等前面加上&符号) 在PHP 中引用的意思是:不同的变量名访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的 ...
- JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解
二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...
- AngularJS select中ngOptions用法详解
AngularJS select中ngOptions用法详解 一.用法 ngOption针对不同类型的数据源有不同的用法,主要体现在数组和对象上. 数组: label for value in a ...
- 【转载】C/C++中extern关键字详解
1 基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.此外extern也可用来进行链接指定. 也就是说extern ...
- oracle中imp命令详解 .
转自http://www.cnblogs.com/songdavid/articles/2435439.html oracle中imp命令详解 Oracle的导入实用程序(Import utility ...
- Android中Service(服务)详解
http://blog.csdn.net/ryantang03/article/details/7770939 Android中Service(服务)详解 标签: serviceandroidappl ...
- python中threading模块详解(一)
python中threading模块详解(一) 来源 http://blog.chinaunix.net/uid-27571599-id-3484048.html threading提供了一个比thr ...
随机推荐
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(40)-精准在线人数统计实现-【过滤器+Cache】
原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(40)-精准在线人数统计实现-[过滤器+Cache] 系列目录 上次的探讨没有任何结果,我浏览了大量的文章 ...
- Delphi检查GetElementByID返回值的有效性
Delphi 可以像JavaScript 脚本语言一样使用GetElementByID方法访问网页中指定ID的元素,一般要配合TWebBrowser组件使用.首先使用TWebBrowser浏览网页,然 ...
- strace使用详解(转) 分类: shell ubuntu 2014-11-27 17:48 134人阅读 评论(0) 收藏
(一) strace 命令 用途:打印 STREAMS 跟踪消息. 语法:strace [ mid sid level ] ... 描述:没有参数的 strace 命令将所有的驱动程序和模块中的 ...
- WebRTC clientICE 延迟问题
本文原创自 http://blog.csdn.net/voipmaker 转载注明出处. 近期在优化WebRTCclient呼叫延迟问题.原因是ICE协议栈在收集地址到探測协商过程花费非常长时间.这 ...
- Instruction (hdu 5083)
Instruction Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Tota ...
- IntelliJ Idea 常用快捷键列表(精简版)
查找快捷键: Ctrl+N 查找类 Ctrl+shift+N 查找文件 Ctrl+B 找变量来源 Ctrl+E 最近打开的文件 Ctrl+Alt+B 选中方法的实现 Ctrl+F7 选中方法(属性. ...
- struts2 OGNL 表达式
一.Struts 2支持以下几种表达式语言: OGNL(Object-Graph Navigation Language),可以方便地操作对象属性的开源表达式语言:JSTL(JSP Standard ...
- 通过WebClient/HttpWebRequest实现http的post/get方法
///<summary>/// 通过WebClient类Post数据到远程地址,需要Basic认证: /// 调用端自己处理异常 ///</summary>///<par ...
- c# 访问修饰符的访问权限
1. 访问修饰符. 指定声明的类型和类型成员的可访问性. (1) public:是类型和类型成员的访问修饰符.公共访问是允许的最高访问级别.对访问公共成员没有限制. (2) private:是一个成员 ...
- php中调用其他系统http接口的方法说明
使用函数: file_get_contents($url); 传入接口url及其参数:如 $url="http://192.168.1.1/test.jsp?id=1&type=2& ...