unity协程_unity 协程理解

Coroutine官方解释:

1e3679c3c4b5286e2678dc2776bdf58d.png

协程是包含可以让出自身执行直到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之外的其他异常(这个语句很简单, 基本不会发生异常), 它的功能也很好理解, 其实就是一个迭代器, 这个迭代器的唯一功能就是返回它管理的子协程的每一次迭代。

通过这个方式, 就可以保证协程能正常返回, 不会被卡住。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值