Unity的协程Coroutine实现原理和C#的IEnumerator、IEnumeratable、yield 介绍

Unity的协程实现一直困扰着我,花了很多时间去翻资料也没看出个所以然,今天又去查阅相关博客,总算有个比较清晰的理解,记录一下。

    想要弄清楚Coroutine的机制,就必须先弄清楚什么是IEnumerator,IEnumeratable,yield。

IEnumerator和IEnumeratable:

    这两个接口的代码非常简单,但是理解起来其实也需要一点思路。先贴出它们的代码:

public interface IEnumerator
{
    object Current {get:}
    bool MoveNext();
    void Reset();
}
public interface IEnumeratable
{
    IEnumerator GetEnumerator();
}

实现IEnumerator的类就可以进行foreach遍历,这个很好理解,但是为什么一个类要能够遍历,除了要继承IEnumerator之外还要继承IEnumeratable呢?这里我觉得C#之所以这样设计,是为了体现面向对象的思想。IEnumerator可以理解为怎么实现枚举的方法,IEnumeratable可以理解为一个对象是否可枚举。

yield:

    yield这个关键字,我觉得是C#里面最难理解的关键字。对于yield的理解一直都很模糊,不知道大家是不是和我一样的状况。今天看到下面这段代码,不仅对Coroutine的理解加深,也对yield的作用有进一步认识。代码如下:

class Test  
{  
    static IEnumerator GetCounter()  
    {  
        for (int count = 0; count < 10; count++)  
        {  
            yield return count;  
        }  
    }  
}  

很简单的代码段,但是看起来很奇怪,count明明是int类型,怎么可以作为IEnumerator的结果进行return呢?之所以会让人觉得奇怪,就是因为这里的yield关键字,让我们看下编译后的代码:

internal class Test  
{  
    // Note how this doesn't execute any of our original code  
    private static IEnumerator GetCounter()  
    {  
        return new <GetCounter>d__0(0);  
    }  
  
    // Nested type automatically created by the compiler to implement the iterator  
    [CompilerGenerated]  
    private sealed class <GetCounter>d__0 : IEnumerator<object>, IEnumerator, IDisposable  
    {  
        // Fields: there'll always be a "state" and "current", but the "count"  
        // comes from the local variable in our iterator block.  
        private int <>1__state;  
        private object <>2__current;  
        public int <count>5__1;  
  
        [DebuggerHidden]  
        public <GetCounter>d__0(int <>1__state)  
        {  
            this.<>1__state = <>1__state;  
        }  
  
        // Almost all of the real work happens here  
        private bool MoveNext()  
        {  
            switch (this.<>1__state)  
            {  
                case 0:  
                    this.<>1__state = -1;  
                    this.<count>5__1 = 0;  
                    while (this.<count>5__1 < 10)        //这里针对循环处理  
                    {  
                        this.<>2__current = this.<count>5__1;  
                        this.<>1__state = 1;  
                        return true;  
                    Label_004B:  
                        this.<>1__state = -1;  
                        this.<count>5__1++;  
                    }  
                    break;  
  
                case 1:  
                    goto Label_004B;  
            }  
            return false;  
        }  
  
        [DebuggerHidden]  
        void IEnumerator.Reset()  
        {  
            throw new NotSupportedException();  
        }  
  
        void IDisposable.Dispose()  
        {  
        }  
  
        object IEnumerator<object>.Current  
        {  
            [DebuggerHidden]  
            get  
            {  
                return this.<>2__current;  
            }  
        }  
  
        object IEnumerator.Current  
        {  
            [DebuggerHidden]  
            get  
            {  
                return this.<>2__current;  
            }  
        }  
    }  
}  

仔细地看上面这段代码,是不是有点理解了!整个GetCounter()函数被编译成一个class!yield之所以让人觉得很奇怪,我想就是怪在这里,不看实现的话,根本就不会知道,也不会想到一个函数会被编译成一个类。让我们再仔细看看上面的代码给了我们什么信息,GetCounter()可以看成一个平常的类,<>2__current就是yield return 的count,this.<>1_state我把它理解为断点位置,GetCounter()里面有多少行yield return 的语句,this.<>1_state就有多少个状态,反应在switch结果里面就是有多少个case。

Coroutine:

    Unity的Coroutine很多资料都讲了,不是线程,它其实也是由运行在Unity引擎下的MainThread线程调用。和Uptate,FixedUpdate这些MonoBehaviour的函数类似,都是由Unity引擎在每帧检查,在适当的时机进行调用的函数。

StartCoroutine():

    在调用StartCoroutine("CoroutineFun")的时候,Unity做了什么呢?可以这样理解,Unity的每一个MonoBehaviour对象,都会保存一个协程栈,调用StartCoroutine时,CoroutineFun便添加进协程栈里,之后每一帧检测协程栈里每一个协程的状态,如果该协程当前可执行,就MoveNext,已经执行完了的,就移除。

yield return new WaitForSecond(2)

    这个语句可以这样理解,Unity在判断协程是否可以调用MoveNext前,会先判断迭代器的object Current值,如果Current的类型是WaitForSecond,那么就判断当前时间是否已经过了2秒,如果是就调用迭代器的MoveNext,如果还没过2秒,就跳过。其他的yield return null语句,用迭代器的原理去理解就可以了。

现在我们把这些知识点串联起来:Unity协程的实现,是基于C#的迭代器,开启一个协程其实就是把一个迭代器放进栈里,然后每帧去判断迭代器的<>2_current和<>_1state是否满足条件,满足条件就执行MoveNext(),直到迭代结束。

    基本就是这些内容,有一些是自己的理解,没有仔细去翻看源代码,希望是对的,哈哈。也希望能帮到大家,谢谢!

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值