在Unity中,协程(Coroutines)的形式是我最喜欢的功能之一,我都会使用它来控制须要定时的。网络
协同程序,在主程序运行的同时,开启另一段逻辑处理,来协同当前程序的执行。
可能看了这段文字介绍仍是有点模糊,其实能够用多线程来比较。多线程
多线程,顾名思义,多条同时执行的线程。
最初,多线程的诞生是为了解决IO阻塞问题,现在多线程能够解决许多一样须要异步方法的问题(例如网络等)。
所谓异步,通俗点讲,就是我走个人线程,你走你的线程。当某个线程阻塞时,另外一个线程不会受影响继续执行。异步
须要认识到的是,多线程并非真正意义上的多条线程同时执行。
它的实际是将一个时间段分红若干个时间片,每一个线程轮流运行一个时间片。函数
(如图,将执行步骤切分红极小的粒度,而后依次运行)线程
可是因为时间片粒度很是很是小,几乎看不出区别,因此程序执行效果跟真正意义上的并行执行效果基本一致。指针
然而多线程有一个坏处,就是可能形成共享数据的冲突。code
假若有一个变量i = 0, Step1_1的操做是进行++i操做,Step2_1的操做是进行--i操做。
咱们预期最终结果i为0。协程
但因为操做切分得太小,可能会发生这样顺序的事:对象
固然多线程的冲突也有解决方案: 互斥锁....
可是这些多多少少会付出额外的代价,让程序变得臃肿。
CPU有多条线程,一条线程能够有多个协程。
协程跟多线程相似,也有相似异步的效果(注意不是真正的异步)。
只不过它的切分粒度不是基于系统划分的时间片,而是基于咱们编写的yield,并且每每粒度更大。
粒度是取决于本身定义何时让协程挂起:
//下面定义了一个协程函数,注意必须使用IEnumerator做为返还值才能成为协程函数。 IEnumerator Test() { for(int i = 0; i<1000 ; ++i){ ans += i; yield return 0;//挂起,下一帧再来从这个位置继续执行。 } j+=2; yield return 0;//挂起,下一帧再来从这个位置继续执行。 ++j; yield return 0;//挂起,下一帧再来从这个位置继续执行。 }
若是划分的粒度过大,协程所在的线程可能在相应的帧卡顿。
甚至若是让协程阻塞(死循环),那么协程所在的整个线程也会阻塞。
所以说协程能够有相似异步的效果,可是不是真正的异步。
协程的一大好处就是能够避免数据访问冲突的问题:
由于它的粒度相对多线程的大不少,因此每每不多出现冲突现象
在上面多线程的例子里,使用协程则能够这样:
对于保证不会阻塞的并行操做且并行性要求不高的并行操做,可使用协程。
更实际来讲,协程最经常使用于延时执行等控制时间轴的操做,例如N秒后调用指定函数。
利用每帧执行一段协程的特性,咱们能够引入个带累加计时判断循环,而后再超过3秒后跳出循环,执行Debug.Log()
//3s后执行Debug.Log IEnumerator Test() { for(float timer = 0.0f; timer < 3.0f ; timer += Time.DeltaTime){ yield return 0;//挂起,下一帧再来从这个位置继续执行。 } Debug.Log("启动协程3s后"); }
可是Unity封装了个更好用的类:WaitForSeconds
使这种延时的协程代码更加简洁。
//本来写法 for(float timer = 0.0f; timer < 3.0f ; timer += Time.DeltaTime){ yield return 0;//挂起,下一帧再来从这个位置继续执行。 } //使用WaitForSeconds的写法 yield return new WaitForSeconds(3.0f);
接下来就展现下,协程使用的示例:
首先编写好协程函数
IEnumerator TestWaitForSeconds() { //3s后执行Debug.Log; yield return new WaitForSeconds(3.0f); Debug.Log("启动协程3s后"); }
而后在某个地方使用StartCoroutine(TestWaitForSeconds())或者StartCoroutine("TestWaitForSeconds")
//启动协程:3s后执行Debug.log StartCoroutine(TestWaitForSeconds()); //启动后,继续往下执行 ...
另一提,Unity还有个同样也是用于延时调用的函数,叫Invoke
Invoke("test",2.0f); \\延时2秒后执行函数test
可是Invock所要调用的函数必须是空类型返还值,还必须得是在当前类里面的方法。
通常来讲,用协程来解决这样的问题已经绰绰有余,并且还有更安全的调用方法而不是只用string类型做为参数的方法,所以不必使用Invoke。
StartCoroutine(string methodName);
StartCoroutine(IEnumerator method);
StopCoroutine(string methodName);//终止指定的协程
StopAllCoroutine();//终止全部协程
//程序在下一帧中从当前位置继续执行 yield return 0; //程序在下一帧中从当前位置继续执行 yield return null; //程序等待N秒后从当前位置继续执行 yield return new WaitForSeconds(N); //在全部的渲染以及GUI程序执行完成后从当前位置继续执行 yield new WaitForEndOfFrame(); //全部脚本中的FixedUpdate()函数都被执行后从当前位置继续执行 yield new WaitForFixedUpdate(); //等待一个网络请求完成后从当前位置继续执行 yield return WWW; //等待一个xxx的协程执行完成后从当前位置继续执行 yield return StartCoroutine(xxx); //若是使用yield break语句,将会致使协程的执行条件不被知足,不会从当前的位置继续执行程序,而是直接从当前位置跳出函数体,回到函数的根部 yield break;
协程函数的返回值时IEnumerator,它是一个迭代器,能够把它当成执行一个序列的某个节点的指针。
它提供了两个重要的接口,分别是Current(返回当前指向的元素)和MoveNext()(将指针向后移动一个单位,若是移动成功,则返回true)。
yield关键词用来声明序列中的下一个值或者是一个无心义的值。
若是使用yield return x(x是指一个具体的对象或者数值)的话, 那么MoveNext返回为true而且Current被赋值为x,若是使用yield break使得MoveNext()返回为false。 若是MoveNext函数返回为true意味着协程的执行条件被知足,则可以从当前的位置继续往下执行。不然不能从当前位置继续往下执行。