當我們在說協程時,我們在說些什么?


能告訴我什么是協程嗎?

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

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

結果:

可以看到當我們執行一次MoveNext方法,才會取得當前當前的值,所以需要循環調用MoveNext才能將全部值取出。

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

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

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

協程該怎么用呢?

啟動協程

首先我們需要定義一個返回IEnumerator的方法,如:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM