天天看點

unity協程_unity 協程了解

Coroutine官方解釋:

unity協程_unity 協程了解

協程是包含可以讓出自身執行直到yield指令執行完畢的一個函數。

public Coroutine StartCoroutine(IEnumerator routine) 
           

協程由上面的函數傳回, StartCoroutine有一個IEnumerator的參數,從這兒就可以看出, 協程和疊代器脫不開關系。事實上, Coroutine的實作離不開C#的疊代器。

C#的疊代器主要有兩個接口, 一個是IEnumerable, 一個IEnumerator, IEnumerable接口隻有一個IEnumerator GetEnumerator()的函數, IEnumerator接口有MoveNext(), Reset(), 和 Current三個成分, 其實c#的List, Dictionary等都實作了這兩個接口, 也正是通過這兩個接口,實作了疊代器的作用,其實IEnumerable可以了解成把IEnumerator包了一層, 通過GetEnumerator函數對同一個對象生成多個互不幹擾的IEnumerator疊代器。當我們生命一個對象是一個IEnumerator時, 編譯器會自動給我們生成對應的疊代器類,比如說:

IEnumerator Init1() 
{     
    for (int i = 0; i < 5; i++)     
    {         
        Debug.LogFormat("init1 enter: {0}, {1}", i, Time.realtimeSinceStartup);  
        yield return new WaitForSeconds(2); //     
    } 
} 
           

Init1是個簡單的協程, 編譯器會根據它的定義一個全新的類:

[CompilerGenerated]
private sealed class <Init1>c__Iterator1 : IEnumerator, IDisposable, IEnumerator<object>
{
    internal int <i>__1;

    internal object $current;

    internal bool $disposing;

    internal int $PC;

    object IEnumerator<object>.Current
    {
        [DebuggerHidden]
        get
        {
            return $current;
        }
    }

    object IEnumerator.Current
    {
        [DebuggerHidden]
        get
        {
            return $current;
        }
    }

    [DebuggerHidden]
    public <Init1>c__Iterator1()
    {
    }

    public bool MoveNext()
    {
        uint num = (uint)$PC;
        $PC = -1;
        switch (num)
        {
        case 0u:
            <i>__1 = 0;
            goto IL_008a;
        case 1u:
            <i>__1++;
            goto IL_008a;
        default:
            {
                return false;
            }
            IL_008a:
            if (<i>__1 < 5)
            {
                Debug.LogFormat("init1 enter: {0}, {1}", <i>__1, Time.realtimeSinceStartup);
                $current = new WaitForSeconds(2f);
                if (!$disposing)
                {
                    $PC = 1;
                }
                break;
            }
            $PC = -1;
            goto default;
        }
        return true;
    }

    [DebuggerHidden]
    public void Dispose()
    {
        $disposing = true;
        $PC = -1;
    }

    [DebuggerHidden]
    public void Reset()
    {
        throw new NotSupportedException();
    }
}
           

這個<Init1>c_Iterator1的類,實作了IEnumerator, IDisposable, IEnumerator<object>接口, 編譯器根據Init1函數體, 寫了一個可以儲存目前疊代狀态(疊代到了第幾個元素)的類。

這個類是編譯器自動生成的, 而我們自己寫的函數Init1函數時, 則變成了這樣:

private IEnumerator Init1()
{
    return new <Init1>c__Iterator1();
}
           

直接傳回一個Init1的疊代器類,是以StartCoroutine(Init1()),就是将一個 <Init1>c__Iterator1()的疊代器執行個體傳給StartCoroutine, 之後的處理就是Unity不公開的部分了, 按照文檔資訊可以推論,這些StartCorountine函數傳回的Coroutine應該都被Unity GameObject管理起來, 在每一幀的不同位置, 找出那些Yeild指令得到滿足的Coroutine, 進行它的下一次疊代。

異常處理

try{}catch(){}語句中是不能包含yeild指令(除了yield break)的,編譯沒法通過:

Cannot yield a value in the body of a try block with a catch clause
           

如果協程裡yield語句之後的操作發生了異常, 這個協程将不會傳回, 如果在父協程裡有這樣一種順序:

IEnumerator Init()
{
    Debug.Log("Pre Start: " + Time.realtimeSinceStartup);
    yield return StartCoroutine(init2);
    Debug.Log("All End: " + Time.realtimeSinceStartup);
    //do something
}

IEnumerator Init2()
{
    for (int i = 0; i < 5; i++)
    {
        yield return new WaitForSeconds(2);
        //gameobject根本沒有camera,這裡會有一個MissingComponentException異常
        gameObject.GetComponent<Camera>().clearFlags = CameraClearFlags.SolidColor;
        Debug.LogFormat("init2 enter: {0}, {1}", i, Time.realtimeSinceStartup);
    }
}
           

這種情況下, Init2函數種,需要傳回2秒後,修改camera的屬性, 但是因為沒有camera, 抛出異常了, 此時, Init函數無法從Init2種傳回,它後面的語句将不再執行。要排除這種情況,有一個辦法就是在可能出異常的地方,單獨catch下:

IEnumerator Init2()
{
    for (int i = 0; i < 5; i++)
    {
        yield return new WaitForSeconds(2);       
        try
        {
            gameObject.GetComponent<Camera>().clearFlags = CameraClearFlags.SolidColor;
            Debug.LogFormat("init2 Try: {0}, {1}", i, Time.realtimeSinceStartup);
        }
        catch (Exception e)

        {
            Debug.LogFormat("init2 exception: {0}, {1}, {2}", i, Time.realtimeSinceStartup, e.Message);
            yield break;
        }
    }
           

這樣子, 就能順利傳回Init, 不影響之後的功能了。但這種方式寫出來的協程會很冗長, 到處都是try ...catch。

解決辦法,就是在unity和協程之間加一個中間層。

IEnumerator InitWrapper(IEnumerator enumerator)
{
    Debug.Log("Pre Start: " + Time.realtimeSinceStartup);
    while (true)
    {
        try
        {
            if (enumerator == null || !enumerator.MoveNext())
            {
                Debug.Log("All End: " + Time.realtimeSinceStartup);
                yield break;
            }
        }
        catch (Exception e)
        {
            Debug.Log("All End: with exception" + Time.realtimeSinceStartup);
            yield break;
        }
        yield return enumerator.Current;
    }
}
           

這個中間協程InitWrapper接管了自協程的疊代,這樣就能catch住除了yield return enumerator.Current之外的其他異常(這個語句很簡單, 基本不會發生異常), 它的功能也很好了解, 其實就是一個疊代器, 這個疊代器的唯一功能就是傳回它管理的子協程的每一次疊代。

通過這個方式, 就可以保證協程能正常傳回, 不會被卡住。