Unity Coroutine详解(一)

Unity 中协程是个很是强大的功能,其做用主要是用于游戏中的延时调用或者执行一连串的有时间间隔的事件流程,例如剧情对话等。简单总结了几点协程相关的知识点,旨在加深记忆,同时为初学者解惑。程序员

 

一、协程、进程与线程面试

这是个面试中常常会问到的问题:协程、进程与线程的区别在哪?网络

  说到协程,咱们首先回顾如下线程与进程这两个概念。在操做系统(os)级别,有进程(process)和线程(thread)两个咱们看不到但又实际存在的“东西”,这两个东西都是用来模拟“并行”的,写操做系统的程序员经过用必定的策略给不一样的进程和线程分配CPU计算资源,来让用户“觉得”几个不一样的事情在“同时”进行“。在单CPU上,是os代码强制把一个进程或者线程挂起,换成另一个来计算,因此,其实是串行的,只是“概念上的并行”。在如今的多核的cpu上,线程多是“真正并行的”。

多线程

进程拥有本身独立的堆和栈,既不共享堆,亦不共享栈,进程由操做系统调度。函数

线程拥有本身独立的栈和共享的堆,共享堆,不共享栈,线程亦由操做系统调度(标准线程是的)。工具

协程和线程同样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。性能

一个应用程序通常对应一个进程,一个进程通常有一个主线程,还有若干个辅助线程,线程之间是平行运行的,在线程里面能够开启协程,让程序在特定的时间内运行。spa

 

协程和线程的区别是:协程避免了无心义的调度,由此能够提升性能,但也所以,程序员必须本身承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。操作系统

打个比方吧,假设有一个操做系统,是单核的,系统上没有其余的程序须要运行,有两个线程 A 和 B ,A 和 B 在单独运行时都须要 10 秒来完成本身的任务,并且任务都是运算操做,A B 之间也没有竞争和共享数据的问题。如今 A B 两个线程并行,操做系统会不停的在 A B 两个线程之间切换,达到一种伪并行的效果,假设切换的频率是每秒一次,切换的成本是 0.1 秒(主要是栈切换),总共须要 20 + 19 * 0.1 = 21.9 秒。若是使用协程的方式,能够先运行协程 A ,A 结束的时候让位给协程 B ,只发生一次切换,总时间是 20 + 1 * 0.1 = 20.1 秒。若是系统是双核的,并且线程是标准线程,那么 A B 两个线程就能够真并行,总时间只须要 10 秒,而协程的方案仍然须要 20.1 秒。线程

 

  其实就根原本说,协程除了名字以外,和线程是没有太大联系的。Unity中的特殊在于全部的脚本和代码都是在一个主线程里运行的,协程也不例外。协程与线程的类似点只在于,协程看起来也能够与其余函数并行执行。 但本质上来讲,线程是经过能够开启多个子线程同时执行程序,而达到并行。而协程则是经过每帧检测的方式,在本身与其余函数之间切换。这种“来回跑”的方式,与Unity中一惯有明确执行顺序的风格(脚本生命周期)看起来不太统一。但这正是它的强大之处,使得咱们在使用协程的时候没必要考虑lock等诸多线程中的问题。

 二、协程执行原理

unity中协程执行过程当中,经过yield return XXX,将程序挂起,去执行接下来的内容,注意协程不是线程,在为遇到yield return XXX语句以前,协程额方法和通常的方法是相同的,也就是程序在执行到yield return XXX语句以后,接着才会执行的是 StartCoroutine()方法以后的程序,走的仍是单线程模式,仅仅是将yield return XXX语句以后的内容暂时挂起,等到特定的时间才执行。
那么挂起的程序何时才执行,这就要看monoBehavior的生命周期了。

也就是协同程序主要是update()方法以后,lateUpdate()方法以前调用的,接下来咱们经过一个小例子去理解一下。

using UnityEngine;
using System.Collections;
using System.Threading;
public class test : MonoBehaviour
{

    void Start()
    {
        StartCoroutine(tt());//开启协程
        for (int i = 0; i < 200; i++)   //循环A
        {
            Debug.Log("*************************" + i);
            Thread.Sleep(10);
        }
    }

    IEnumerator tt()
    {
        for (int i = 0; i < 100; i++) //循环B
        {
            Debug.Log("-------------------" + i);
        }

        yield return new WaitForSeconds(1); //协程1

        for (int i = 0; i < 100; i++) //循环C
        {
            Debug.Log(">>>>>>>>>>>>>>>>>>>>" + i);
            yield return null; //协程1
        }
    }

    // 更新数据
    void Update()
    {
        Debug.Log("Update");
    }

    //晚于更新
    void LateUpdate()
    {
        Debug.Log("------LateUpdate");
    }

}

程序的运行结果为:

先执行循环B,而后执行循环A,而后执行update()和lateUpdate()的方法,等待1S以后,在updat()和lateupda()之间执行循环C的输出。

 

三、yield return 的不一样返回类型

  使用yield return的时候你会发现它能够返回的类型一长串,对于初学者我以为就分为带 new和不带new的就好了。

  先说不带new的。一般能够yield return的有 null,数字 ,字符串,布尔值甚至表达式,函数,嵌套协程等。

  以在Start()中开启当前协程为例,若是是不带new的返回类型,执行时间都是同样的。即在第一时间执行协程中的代码 到第一个yield return当行为止,而后在下一帧的Update以后,LateUpdate以前执行yield return后面的代码。

  另外须要注意的是,yield return后面能够是一个函数调用,赋值表达式,嵌套的其它协程等。以赋值的表达式num=10为例;它会在当行yield return执行的时候就执行,函数调用和其它协程也是同样。也就是说,此时yield return的函数调用就至关于直接调用了这个函数,而且是当时就执行的。 而其它return 类型 如null,字符串,数字等通常只用做延迟一帧来用,其它做用,待我后期再研究下。

  下面说带new的,也是一般咱们重点使用的协程功能。

   这里列举几个:

  (1)new  WaitUntil(Func<bool>)  参数是一个布尔返回类型的委托,做用是,知道这个返回的布尔值为true时,协程才会继续执行当行yield return 后面的代码。       

  (2) new WaitForSeconds(float)参数是float类型的数字,表示秒,也是协程最经常使用的功能之一。 做用是,在N秒后才会继续执行当行yield return 后面的代码。 

因为yield return能够在一个协程中任意位置写多个,配合这个能够实现不少时间细化可视化的功能。

  (3)new WaitForEndOfFrame()做用是,在结束当前帧 摄像机和GUI被渲染以及其它函数完成后才会继续执行当行yield return 后面的代码。 这个我只验证了在LateUpdate执行完以后执行,具体在整个脚本周期中哪一个函数执行完以后开始执行还未详细验证。

  (4)new  WaitForFixedUpdate()  做用是,直到当行代码以后第一个FixedUpdate执行以后才会继续执行当行yield return 后面的代码。也就是说,若是是在start里面开启协程的话,第一次执行FixedUpdate以后就会继续执行return后面的代码。

  后面还有许多类型的 返回,没有一一验证,不过做用应该大同小异,即在执行第一个该类型动做以后才会继续执行当行yield return 后面的代码。

  值得一提的是,协程的延迟调用和非阻塞式挂起是用于网络请求等高级结构很好的工具,很是值得花一些时间去仔细研究。

四、开始协程

  经过MonoBehaviour提供的StartCoroutine方法来实现启动协同程序。

  一、StartCoroutine(IEnumerator routine);

  优势:灵活,性能开销小。

  缺点:没法单独的中止这个协程,若是须要中止这个协程只能等待协同程序运行完毕或则使用StopAllCoroutine();方法。

  二、StartCoroutine (methodName:string, value : object = null);

  优势:能够直接经过传入协同程序的方法名来中止这个协程:StopCoroutine(string methodName);

  缺点:性能的开销较大,只能传递一个参数。

 

五、中止协程

  协程内中止能够用yield return break;

  协程外:

  一、StopCoroutine(string methodName);

  二、StopAllCoroutine();

  三、设置gameobject的active为false时能够终止协同程序,可是再次设置为true后协程不会再启动。设置当前协程所在脚本enable为false也并不能中止当前协程的执行。

相关文章
相关标签/搜索