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(),直到迭代结束。

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

 

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我了解了你的要求。关于Unity协程Coroutine的使用,我可以为你介绍一下。 CoroutineUnity中的一种协作式线程实现方式,它可以在执行一段代码时主动中断执行,等待特定条件而不阻塞主线程。在Unity中,协程通常用于异步操作、延迟执行、动画演示等场景。 Unity中启动协程Coroutine的方法是使用StartCoroutine()函数,并传递一个IEnumerator类型的对象参数给它。IEnumerator作为协程的核心,它定义了一种迭代器的行为,可以通过yield语句来指定协程执行的流程。 比如,下面的代码中是一个简单的协程实现: ``` IEnumerator ExampleCoroutine() { Debug.Log("start"); yield return new WaitForSeconds(1.0f); Debug.Log("wait 1s"); yield return new WaitForSeconds(2.0f); Debug.Log("wait 2s"); yield return null; Debug.Log("end"); } ``` 其中,yield return new WaitForSeconds(1.0f)表示等待1秒后继续执行协程yield return null表示协程结束。 启动该协程可以使用StartCoroutine(ExampleCoroutine())方法,这将在不阻塞主线程的情况下执行协程的逻辑。 对于多协程并行执行的情况,可以使用yield return StartCoroutine()的方式来实现,如下所示: ``` IEnumerator CoroutineA() { for (int i = 0; i < 10; i++) { Debug.Log("CoroutineA: " + i); yield return null; } } IEnumerator CoroutineB() { for (int i = 0; i < 5; i++) { Debug.Log("CoroutineB: " + i); yield return null; } } IEnumerator CoroutineC() { yield return StartCoroutine(CoroutineA()); yield return StartCoroutine(CoroutineB()); Debug.Log("CoroutineC end"); } ``` 在上面的示例中,CoroutineC使用了yield return StartCoroutine()的方式来启动多个协程,并且等待它们全部执行结束后再继续执行。这样可以实现多个协程并发执行,同时确保它们按照正确的顺序执行。 希望以上内容对你有帮助。如果有其他问题,请随时提出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值