當咱們在說協程時,咱們在說些什麼?

能告訴我什麼是協程嗎?

協程的官方定義是一種具備暫停執行並將控制權返回給Unity,待下一幀時繼續執行。通俗點講就是,協程是一種能夠分部執行的函數,即該函數不是每次調用時都會執行函數體內的所有方法,而是隻調用其中部分代碼。寫到這裏不知道您有沒有發現,該定義有點像IEnumerator的延遲執行。舉一個例子:php

void Start ()
{
    IEnumerator numbers = YieldThreeNumbers ();
    for (int i = 0; i < 3; i++)
        {
        if(!numbers.MoveNext())    
            break;
        Debug.Log((int)numbers.Current);
    }
}

IEnumerator YieldThreeNumbers()
{
    yield return 1;
    yield return 2;
    yield return 3;
}
View Code

結果:html

能夠看到當咱們執行一次MoveNext方法,纔會取得當前當前的值,因此須要循環調用MoveNext才能將所有值取出。ios

協程也是一樣的方法:每一幀都會調用MoveNext方法,期間保存了下一次執行的位置,等到下一幀時會在該位置繼續執行。web

PS: 在C#中,yield和IEnumerator一塊兒使用時其實是實現了Itertor模式,詳情可參考這篇文章。http://csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx多線程

由此也能夠看到,協程其實與多線程一點關係都沒有。協程是在主線程中執行的,且每次只能執行一個協程。app

協程該怎麼用呢?

啓動協程

首先咱們須要定義一個返回IEnumerator的方法,如:ide

IEnumerator GetEnumerator()
{
    Yield return null;
}
View Code

而後在調用StarCoroutine方法,其簽名以下:函數

Coroutine StartCoroutine(string methodName,object value=null);性能

Coroutine StartCoroutine(IEnumerator routine);學習

在是使用第一個方法時,咱們直接將傳入上面定義的方法名:

StartCoroutine(「GetEnumerator」);

注意該方法的參數是IEnumerator,因此咱們能夠直接將調用上面定義的方法的返回值傳入:

StartCoroutine(GetEnumertor());

下面看一個來自官網的例子:

IEnumerator Fade()
{
    for (float f = 1f; f >= 0; f -= 0.1f)
    {
        Color c = renderer.material.color;
        //減小a值,即透明度
        c.a = f;
        renderer.material.color = c;
        yield return null;
    }
}

void Update()
{
    if (Input.GetKeyDown("f"))
    {
        //沒按一次"f"鍵,gameObject的透明度都在減小,實現漸隱的效果
        StartCoroutine("Fade");
}
    
View Code

固然,咱們不單單能夠yield null,還能夠yield其它的表達式:

1. 其它的數值,如0:

和null相似,不過不推薦。由於會存在裝箱,拆箱的問題,或多或少會影響性能。

2. WaitForEndOfFrame

等待至全部的Camera和GUI都呈現好了以後執行。

3. WaitForFixedUpdate

等待至全部物理都計算後執行

4. WaitForSeconds

在指定時間段內不執行

5. WWW

等待一個web請求完成

6. 另外一個協程

這是比較有意思的一點。由於StartCoroutine的返回值是Coroutine,因此咱們能夠yield另外一個協程。

要注意的是,若是設置Time.timeScale爲0時,yield return new WaitForSeconds(x)是不會恢復繼續執行。

中止協程

void StopCoroutine(string methodName);

void StopCoroutine(IEnumerator routine);

其中StopCortouine(string methodName)只能中止由與之相對應的StarCoroutine(string methodName)啓動的協程。

還有其它的方法來中止協程,但都不是中止某個指定的協程,而是中止多個協程。

void StopAllCoroutines()

中止該behavior內的所有協程

void SetActive(bool value);

將behavior的active設爲false後,其內部的協程也都會中止。

等等,我以爲還少了點什麼...

協程能夠將一個方法,放到多個幀內執行,在很大程度上提升了性能。但協程也是有缺陷的:

  1. 不支持返回值;
  2. 不支持異常處理;
  3. 不支持泛型;
  4. 不支持鎖;

下面咱們來解決前三個問題,爲協程添加返回值、異常處理和泛型。關於第四個問題的解決方式,請參考最下方的連接:

返回值:

public class ReturnValueCoroutine
{
    private object result;
    public object Result
    {
        get {return result;}
    }
    public UnityEngine.Coroutine Coroutine;
    
    public IEnumerator InternalRoutine(IEnumerator coroutine)
    {
        while(true)
        {
            if(!coroutine.MoveNext()){
                yield break;
            }
            object yielded = coroutine.Current;
            
            if(yielded != null){
                result = yielded;
                yield break;
            }
            else{
                yield return coroutine.Current;
            }
        }
    }
}

public class Demo : MonoBehaviour
{
    IEnumerator Start ()
    {
        ReturnValueCoroutine myCoroutine = new ReturnValueCoroutine ();
        myCoroutine.Coroutine = StartCoroutine (myCoroutine.InternalRoutine(TestNewRoutine()));
        yield return myCoroutine.Coroutine;
        Debug.Log (myCoroutine.Result);
    }
    
    IEnumerator TestNewRoutine()
    {
        yield return 10;
    }
}
View Code

泛型:

public static class MonoBehaviorExt
{
    public static Coroutine<T> StartCoroutine<T>(this MonoBehaviour obj, IEnumerator coroutine){
        Coroutine<T> coroutineObject = new Coroutine<T>();
        coroutineObject.coroutine = obj.StartCoroutine(coroutineObject.InternalRoutine(coroutine));
        return coroutineObject;
    }
}

public class Coroutine<T>{
    private T result;
    public T Result
    {
        get {return result;}
    }
    public Coroutine coroutine;
    
    public IEnumerator InternalRoutine(IEnumerator coroutine){
        while(true){
            if(!coroutine.MoveNext()){
                yield break;
            }
            object yielded = coroutine.Current;
            
            if(yielded != null && yielded.GetType() == typeof(T)){
                result = (T)yielded;
                yield break;
            }
            else{
                yield return coroutine.Current;
            }
        }
    }
}

public class Demo : MonoBehaviour
{    
    Coroutine<int> routine;
    IEnumerator Start () {
        routine = this.StartCoroutine<int>(TestNewRoutine()); //Start our new routine
        yield return routine; // wait as we normally can
    }
    
    IEnumerator TestNewRoutine(){
        yield return null;
        yield return new WaitForSeconds(2f);
        yield return 10;
    }

    void Update()
    {
        //由於延時,因此要等待一段時間才能顯示
        Debug.Log(routine.Result); 
    }
}
View Code

異常處理:

public static class MonoBehaviorExt
{
    public static Coroutine<T> StartCoroutine<T>(this MonoBehaviour obj, IEnumerator coroutine){
        Coroutine<T> coroutineObject = new Coroutine<T>();
        coroutineObject.coroutine = obj.StartCoroutine(coroutineObject.InternalRoutine(coroutine));
        return coroutineObject;
    }
}

public class Coroutine<T>{
    public T Result {
        get{
            if(e != null){
                throw e;
            }
            return result;
        }
    }
    private T result;
    private Exception e;
    public UnityEngine.Coroutine coroutine;
    
    public IEnumerator InternalRoutine(IEnumerator coroutine){
        while(true){
            try{
                if(!coroutine.MoveNext()){
                    yield break;
                }
            }
            catch(Exception e){
                this.e = e;
                yield break;
            }
            object yielded = coroutine.Current;
            if(yielded != null && yielded.GetType() == typeof(T)){
                result = (T)yielded;
                yield break;
            }
            else{
                yield return coroutine.Current;
            }
        }
    }
}

public class Demo : MonoBehaviour
{
    IEnumerator Start () {
        var routine = this.StartCoroutine<int>(TestNewRoutineGivesException());
        yield return routine.coroutine;
        try{
            Debug.Log(routine.Result);
        }
        catch(Exception e){
            Debug.Log(e.Message);
            // do something
            Debug.Break();
        }
    }
    
    IEnumerator TestNewRoutineGivesException(){
        yield return null;
        yield return new WaitForSeconds(2f);
        throw new Exception("Bad thing!");
    }
}
View Code

你說的我都知道了,還有別的嗎?

1. 使用lamada表達式接受返回值:http://answers.unity3d.com/questions/207733/can-coroutines-return-a-value.html

2. Wrapping Unity C# Coroutines for Exception Handling, Value Retrieval, and Locking:http://zingweb.com/blog/2013/02/05/unity-coroutine-wrapper/

3. CoroutineScheduler:http://wiki.unity3d.com/index.php?title=CoroutineScheduler

 

以上是本人的學習成果及平時收集的資料,若是您有其它更好的資源或是想法,請怒砸至評論區,多多益善!

 

參考資料:Coroutines – More than you want to know,http://twistedoakstudios.com/blog/Post83_coroutines-more-than-you-want-to-know

相關文章
相關標籤/搜索