关于Coroutine的理解

Coroutine难以理解,原因在于它的设计,可以说是巧妙的利用了一个毫无关系的语法特性(yield)。理解Coroutine系统,最重要的是yield关键字,很多人被这个关键字迷惑,因为传统的程序里很少见到这个语法,而在Coroutine里面,用的还不是这个关键字的本意。造成了它的字面含义和他做的事情有区别。我们先看看这个关键字的本来作用:yield是用于生成列表(准确地说是枚举器)。定义一个列表,可以有两种方法,最朴素的就是把它里面的元素全部写出来,比如[1, 2, 3, 4]。传统语言,比如C,Java里面的列表,也只支持这么做。这种方法比较直观,容易理解,但是有些特殊的情况下(比如你想定义一个全部整数的无限长列表),就没法这么做了。枚举(IEnumerable)就是另一种定义的方法,只要我们可以把这个列表里的元素一个一个的列出来,就可以定义这个列表。注意这个“可以”不代表一定要列出,使用这个列表的人也不一定总是需要列表中全部的项。yield是什么呢?就是C#为了方便编写这种“可以”枚举的列表,引入的关键字。可以yield的函数返回值是一个列表,拿到这个列表的人,每次需要用到列表里的一项时,就会执行这个函数(从函数开始的地方或者上一次yield的地方),当执行过程遇到一个yield的时候,就会暂停当前函数的执行,把这个yield的值返回出去。Coroutine是什么?你可以把它看作一个“等待”的列表,每次yield,就是等一个事情发生或完成,比如等待一帧、等待下载图片完成、等待加载资源完成等等。然后,会有一个“守望者”,他的任务,就是拿到这样一个列表,取出第一项,等它完成,取出第二项,等它完成……这样一直进行下去直到列表遍历完。在Unity里面,MonoBehaviour.StartCoroutine就是这个“守望者”。
  总结一下:Coroutine是一个列表,列表里是一系列要等待的事情,然后交给MonoBehaviour.StartCoroutine,让它帮你去等。yield关键字的特点是在生成列表时,到用某一项的时候才去生成某一项。WWW这个类做的事,是在另一个线程中开始下载一个资源,然后等待这个资源下载完成。以上3点,大部分时候是同时使用的,所以你觉得概念比较复杂,不妨试着分开理解这三部分。对于Coroutine,你可以不用yield,用List.GetEnumerator方法,也是可以StartCoroutine的。对于yield,可以去写一些非Coroutine的列表,比如模仿Linq里面的一些方法(Select、Where、Take、Skip)。对于WWW,你可以试着自己实现兼容Coroutine系统的异步操作。对于这3点分别有一定理解之后,再放在一起看的时候,就会更明白了。
  Unity的协程机制实现的一个非常巧妙的地方(或者应该说是C#语言设计的巧妙,因为要在C#里实现协程,使用IEnumerator是最直接最优雅的方式)在于,yield这个关键字也被其他语言用在协程里,比如lua。yield是一个多义词,在C#的迭代器上下文里它的意思更多的是“产出”,而用在协程的上下文里它的意思是“弃权”,即放弃代码流的执行权(线程的控制权),交给其他协程去使用。
  比如这个例子,有一家招待所,一共有好几十间客房(协程),但是老板很抠门,只请了一个扫地阿姨(主线程)。每天一大早(每帧),阿姨就会开始清扫房间。不同房间的顾客有不同的习惯,有的每天都需要打扫,有的隔天打扫一次,有的只在前晚喝大了之后才需要打扫,有的看心情(协程yield return的结果描述了这一习惯,即,希望yield return语句之后的逻辑何时被执行)。阿姨记不住这些顾客的习惯,但她只需要每天依次敲开每间房间的门,问一问顾客是否需要清扫房间就可以了(协程调度器轮询所有「习惯」,也即yield return的结果,看看是否应该继续执行这一协程)。至于顾客,做什么事情是他的自由(www完全可以单开一个线程自己去下载资源,包装成协程是为了方便你使用)。
  WWW的封装只是为了方便你使用,因为Unity其实是希望用户尽可能避免使用线程的,网络通讯这种几乎非多线程不可的部分他就帮你包装成协程,内部实现依然是多线程的,只不过协程在主线程上运行时每帧去轮询通讯线程下载是否完成了。Unity里Texture只能在主线程上创建,所以WWW还是等通讯线程下载完成后在主线程上创建Texture。如果你想要绕开WWW,实现思路其实是一样的,比如开个线程,在里面跑个WebClient,等收到response后再回到主线程创建Texture。
unity的主逻辑其实是一个单线程,但是遇到像资源载入、WWW之类的这种耗费时间的,unity是通过一个线程池来单独跑,然后每帧的时候去检查这个操作是否完成。具体以yield return www为例,当你在一个coroutine里返回这句话的时候,Unity会保存这个函数的上下文(而不是像普通函数一样,直接销毁函数里的临时变量),然后在线程池里找一个来运行对应的逻辑,例如访问网络;在每一帧update里,如果没完成就不管,如果完成了就将数据返回,恢复上下文信息继续运行~这样的好处在于,IO这种时间很长的操作不会卡住主线程,因为这是在另外的线程里跑,主线程每一帧只需要查询一下是否完成即【只不过他包装成了协程的形式,程序员写起来好写】题主你还可以看看UnityEngine.YieldInstruction下的类,如WaitForSeconds, WaitForFixedUpdate(还有Resouces异步加载之类),都是引擎根据你yield return回来的值特殊处理的~例如yield return null就是最普通的,这一帧保存上下文返回、下一帧正常运行下去。。。
还有以下参考资料:
https://docs.unity3d.com/Manual/ExecutionOrder.html,http://www.cnblogs.com/murongxiaopifu/p/4437432.html
(文章内容大都转载自知乎https://www.zhihu.com/question/34878524)

   附上额外的:http://blog.uwa4d.com/archives/USparkle_Coroutine.html

////////////////////////////////////////更新一点协程的嵌套使用/////////////////////////////////
测试用例:

using UnityEngine;
using System.Collections;

public class TestCoroutine : MonoBehaviour {

    void Start () {
        StartCoroutine(TestA());
    }

    IEnumerator TestA()
    {
        Debug.LogError("A1");
        yield return StartCoroutine(TestB());
        Debug.LogError("A2");
    }
    IEnumerator TestB()
    {
        Debug.LogError("B1");
        yield return new WaitForSeconds(1);
        Debug.LogError("B2");
    }

    void Update () {
    
    }
}

关于Coroutine的理解_第1张图片
Paste_Image.png

将TestCoroutine 脚本绑在一个GameObject上直接运行游戏就能在控制台看到结果了,结论很明显:上个协程继续执行需要下个协程完全执行完。

延时功能的几种实现形式:http://www.cnblogs.com/xifarm/p/invoke.html

再更新一点:

using UnityEngine;
using System.Collections;

public class TestCoroutine : MonoBehaviour {
    private int m = 0;
    bool FunA()
    {
        Debug.LogError("Bool Succeed");
        return true;
    }
    int FunB()
    {
        Debug.LogError("Int Succeed");
        return 1;
    }
    void Start () {
        Debug.LogError("int继承于:" + typeof(int).BaseType.Name);
        Debug.LogError("string继承于:"+typeof(string).BaseType.Name);
        FunA();
        FunB();
        StartCoroutine(TestA());
        Debug.LogError("A3");
    }

    IEnumerator TestA()
    {
        Debug.LogError("A1");
        yield return StartCoroutine(TestB());
        Debug.LogError("A2");
    }
    IEnumerator TestB()
    {
        Debug.LogError("B1");
        if (m==0)
        {
            yield return new WaitForSeconds(1);
            Debug.LogError("B2");
        }       
        Debug.LogError("B3");
    }

}

结果:

关于Coroutine的理解_第2张图片
coroutine.png

你可能感兴趣的:(关于Coroutine的理解)