【转】深入剖析Unity协程的实现原理

用过Unity的应该都知道协程,今天就给大家来讲解下这个简洁又神奇的设计。一般的使用场景就是需要异步执行的时候,比如下载、加载、事件的延时触发等,函数的返回值是IEnumerator类型,开启一个协程只需要调用StartCoroutine即可,之后Unity会在每一次GameLoop的时候调用协程,具体的时间点可以看文档

说了这么多我们来看下官方对协程给出的定义:

A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.

即协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。

Demo

前面的说的都是概念性的东西,为了给大家更直观的感受,直接上Demo。

private void Start()
{
    StartCoroutine(TestEnumerator());
}

private IEnumerator TestEnumerator()
{
UnityEngine.Debug.Log(“wait for 1s”);
yield return new WaitForSeconds(1f);
UnityEngine.Debug.Log(“wait for 2s”);
yield return new WaitForSeconds(2f);
UnityEngine.Debug.Log(“wait for 3s”);
yield return new WaitForSeconds(3f);
}

上面的执行结果是:

wait for 1s
等待了一秒    
wait for 2s
等待了两秒
wait for 3s
等待了三秒

Yield是什么

看了上面的Demo细心的各位有没有这样的疑惑。

  1. return前面怎么有个yield关键字。
  2. TestEnumerator函数的返回值是IEnumerator类型但是返回的对象并不是该类型。

为了解释这些问题我们先来看下函数的返回值IEnumerator类型的定义:

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

其实,C#为了简化我们创建枚举器的步骤,你想想看你需要先实现 IEnumerator 接口,并且实现 Current,、MoveNext、Reset 步骤。C#从2.0开始提供了有yield组成的迭代器块,编译器会自动更具迭代器块创建了枚举器。不信,用Reflector反编译看看:

[CompilerGenerated]
private sealed class <TestEnumerator>d__1 : IEnumerator<object>, IEnumerator, IDisposable
{
    private int <>1__state;
    private object <>2__current;
    public Test <>4__this;
<span class="p">[</span><span class="n">DebuggerHidden</span><span class="p">]</span>
<span class="k">public</span> <span class="p">&lt;</span><span class="n">TestEnumerator</span><span class="p">&gt;</span><span class="nf">d__1</span><span class="p">(</span><span class="kt">int</span> <span class="p">&lt;&gt;</span><span class="m">1</span><span class="n">__state</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">this</span><span class="p">.&lt;&gt;</span><span class="m">1</span><span class="n">__state</span> <span class="p">=</span> <span class="p">&lt;&gt;</span><span class="m">1</span><span class="n">__state</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">private</span> <span class="kt">bool</span> <span class="nf">MoveNext</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">switch</span> <span class="p">(</span><span class="k">this</span><span class="p">.&lt;&gt;</span><span class="m">1</span><span class="n">__state</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">case</span> <span class="m">0</span><span class="p">:</span>
            <span class="k">this</span><span class="p">.&lt;&gt;</span><span class="m">1</span><span class="n">__state</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
            <span class="n">UnityEngine</span><span class="p">.</span><span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"wait for 1s"</span><span class="p">);</span>
            <span class="k">this</span><span class="p">.&lt;&gt;</span><span class="m">2</span><span class="n">__current</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">WaitForSeconds</span><span class="p">(</span><span class="m">1f</span><span class="p">);</span>
            <span class="k">this</span><span class="p">.&lt;&gt;</span><span class="m">1</span><span class="n">__state</span> <span class="p">=</span> <span class="m">1</span><span class="p">;</span>
            <span class="k">return</span> <span class="k">true</span><span class="p">;</span>

        <span class="k">case</span> <span class="m">1</span><span class="p">:</span>
            <span class="k">this</span><span class="p">.&lt;&gt;</span><span class="m">1</span><span class="n">__state</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
            <span class="n">UnityEngine</span><span class="p">.</span><span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"wait for 2s"</span><span class="p">);</span>
            <span class="k">this</span><span class="p">.&lt;&gt;</span><span class="m">2</span><span class="n">__current</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">WaitForSeconds</span><span class="p">(</span><span class="m">2f</span><span class="p">);</span>
            <span class="k">this</span><span class="p">.&lt;&gt;</span><span class="m">1</span><span class="n">__state</span> <span class="p">=</span> <span class="m">2</span><span class="p">;</span>
            <span class="k">return</span> <span class="k">true</span><span class="p">;</span>

        <span class="k">case</span> <span class="m">2</span><span class="p">:</span>
            <span class="k">this</span><span class="p">.&lt;&gt;</span><span class="m">1</span><span class="n">__state</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
            <span class="n">UnityEngine</span><span class="p">.</span><span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"wait for 3s"</span><span class="p">);</span>
            <span class="k">this</span><span class="p">.&lt;&gt;</span><span class="m">2</span><span class="n">__current</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">WaitForSeconds</span><span class="p">(</span><span class="m">3f</span><span class="p">);</span>
            <span class="k">this</span><span class="p">.&lt;&gt;</span><span class="m">1</span><span class="n">__state</span> <span class="p">=</span> <span class="m">3</span><span class="p">;</span>
            <span class="k">return</span> <span class="k">true</span><span class="p">;</span>

        <span class="k">case</span> <span class="m">3</span><span class="p">:</span>
            <span class="k">this</span><span class="p">.&lt;&gt;</span><span class="m">1</span><span class="n">__state</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
            <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">object</span> <span class="n">IEnumerator</span><span class="p">.</span><span class="n">Current</span>
<span class="p">{</span>
    <span class="p">[</span><span class="n">DebuggerHidden</span><span class="p">]</span>
    <span class="k">get</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.&lt;&gt;</span><span class="m">2</span><span class="n">__current</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="p">...</span>

}

从中可以得出:

  • yield是个语法糖,编译过后的代码看不到yield

  • 编译器在内部创建了一个枚举类 <TestEnumerator>d__1

  • yield return 被声明为枚举时的下一项,即Current属性,通过MoveNext方法来访问结果

关于更多IEnumerator/yield的细节,推荐参考《深入理解C#》一书的第六章:“实现迭代器的捷径”


StartCoroutine

好了,既然我们已经搞明白了IEnumerator对象是怎么来的了,现在我们就来看看Unity引擎拿到了该对象后是如何处理的吧。

Unity的代码架构是分为三层的,不知道的同学可以看我之前写的一篇文章 Unity中的代码架构

源码分析

Test.cs(Unity逻辑层)
private void Start()
{
    StartCoroutine(TestEnumerator());
}

在Unity的逻辑层进入StartCoroutine的定义你会看到如下代码:

namespace UnityEngine
{
    public class MonoBehaviour : Behaviour
    {
        ...
        public Coroutine StartCoroutine(IEnumerator routine);
        public Coroutine StartCoroutine(string methodName);
        ...
    }
}

发现这些代码已经被封装好编译成了.dll文件,如果想看到具体实现可以在git上获取源码(Unity官方公布了中间层的代码,但是还未公布底层C++的代码)。

MonoBehavior.bindings.cs(Unity中间层)

当你下载好中间层的源码后发现,最核心的实现StartCoroutineManaged2竟然是个被extern修饰的外部函数。

extern Coroutine StartCoroutineManaged(string methodName, object value);
extern Coroutine StartCoroutineManaged2(IEnumerator enumerator);

public Coroutine StartCoroutine(string methodName)
{
object value = null;
return StartCoroutine(methodName, value);
}

public Coroutine StartCoroutine(IEnumerator routine)
{
if (routine == null)
throw new NullReferenceException(“routine is null”);

<span class="k">if</span> <span class="p">(!</span><span class="nf">IsObjectMonoBehaviour</span><span class="p">(</span><span class="k">this</span><span class="p">))</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentException</span><span class="p">(</span><span class="s">"Coroutines can only be stopped on a MonoBehaviour"</span><span class="p">);</span>

<span class="k">return</span> <span class="nf">StartCoroutineManaged2</span><span class="p">(</span><span class="n">routine</span><span class="p">);</span>

}

MonoBehavior.cpp(Unity底层)

通过各种途径的尝试终于获得了Unity的底层源码 \(^o^)/,这里因为版权问题大家还是自行从网络渠道获取吧。

MonoBehaviour::StartCoroutineManaged2(ScriptingObjectPtr enumerator)
{
    Coroutine* coroutine = CreateCoroutine(enumerator, SCRIPTING_NULL);
    return 封装过的Coroutine对象;
}

Coroutine* MonoBehaviour::CreateCoroutine(ScriptingObjectPtr userCoroutine, ScriptingMethodPtr method)
{
获取moveNext;
获取current;

<span class="n">Coroutine</span><span class="o">*</span> <span class="n">coroutine</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Coroutine</span> <span class="p">();</span>
<span class="err">初始化</span><span class="n">coroutine</span><span class="err">对象</span><span class="p">;</span>    <span class="c1">//这个时候就会把moveNext和current传递给coroutine对象</span>

<span class="n">m_ActiveCoroutines</span><span class="p">.</span><span class="n">push_back</span> <span class="p">(</span><span class="o">*</span><span class="n">coroutine</span><span class="p">);</span>
<span class="n">m_ActiveCoroutines</span><span class="p">.</span><span class="n">back</span> <span class="p">().</span><span class="n">Run</span> <span class="p">();</span>
<span class="p">...</span>
<span class="k">return</span> <span class="n">coroutine</span><span class="p">;</span>

}

Coroutine.cpp(Unity底层)
void Coroutine::Run ()
{
    // - Call MoveNext (处理迭代器块的逻辑直到遇到yield return)
    // - Call Current (返回一个条件,何时可以执行下一个moveNext)
<span class="c1">//根据IEnumerator的特性,首先得调用下MoveNext,这样current就被赋值了</span>
<span class="kt">bool</span> <span class="n">keepLooping</span> <span class="o">=</span> <span class="n">InvokeMoveNext</span><span class="p">(</span><span class="o">&amp;</span><span class="n">exception</span><span class="p">);</span>    

<span class="n">ProcessCoroutineCurrent</span><span class="p">();</span>

}

void Coroutine::ProcessCoroutineCurrent()
{
//调用Current,并从中取出yield return的返回对象monoWait
ScriptingInvocation invocation(m_Current);

ScriptingObjectPtr monoWait = invocation.Invoke(&exception);

<span class="c1">//yield return null</span>
<span class="k">if</span> <span class="p">(</span><span class="n">monoWait</span> <span class="o">==</span> <span class="n">SCRIPTING_NULL</span><span class="p">)</span>
<span class="p">{</span>
    <span class="p">...</span>
    <span class="c1">//wait的时间就是0,相当于等一帧</span>
    <span class="n">CallDelayed</span> <span class="p">(</span><span class="n">ContinueCoroutine</span><span class="p">,</span> <span class="n">m_Behaviour</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">F</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">F</span><span class="p">,</span> <span class="n">CleanupCoroutine</span><span class="p">,</span> <span class="n">DelayedCallManager</span><span class="o">::</span><span class="n">kRunDynamicFrameRate</span> <span class="o">|</span> <span class="n">DelayedCallManager</span><span class="o">::</span><span class="n">kWaitForNextFrame</span><span class="p">);</span>
    <span class="k">return</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">HandleIEnumerableCurrentReturnValue</span><span class="p">(</span><span class="n">monoWait</span><span class="p">);</span>

}

void Coroutine::HandleIEnumerableCurrentReturnValue(ScriptingObjectPtr monoWait)
{
ScriptingClassPtr waitClass = scripting_object_get_class (monoWait, GetScriptingTypeRegistry());
const CommonScriptingClasses& classes = GetMonoManager ().GetCommonClasses ();

<span class="c1">//yield return new WaitForSeconds()</span>
<span class="k">if</span> <span class="p">(</span><span class="n">scripting_class_is_subclass_of</span> <span class="p">(</span><span class="n">waitClass</span><span class="p">,</span> <span class="n">classes</span><span class="p">.</span><span class="n">waitForSeconds</span><span class="p">))</span>
<span class="p">{</span>
    <span class="kt">float</span> <span class="n">wait</span><span class="p">;</span>
    <span class="err">通过</span><span class="n">monoWait</span><span class="err">获取需要</span><span class="n">wait</span><span class="err">的时间</span><span class="p">;</span>
    <span class="n">CallDelayed</span><span class="p">(</span><span class="n">ContinueCoroutine</span><span class="p">,</span> <span class="n">m_Behaviour</span><span class="p">,</span> <span class="n">wait</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">F</span><span class="p">,</span> <span class="n">CleanupCoroutine</span><span class="p">,</span> <span class="n">DelayedCallManager</span><span class="o">::</span><span class="n">kRunDynamicFrameRate</span> <span class="o">|</span> <span class="n">DelayedCallManager</span><span class="o">::</span><span class="n">kWaitForNextFrame</span><span class="p">);</span>
    <span class="k">return</span><span class="p">;</span>  
<span class="p">}</span>

<span class="c1">//yield reuturn new WaitForFixedUpdate()</span>
<span class="k">if</span> <span class="p">(</span><span class="n">scripting_class_is_subclass_of</span> <span class="p">(</span><span class="n">waitClass</span><span class="p">,</span> <span class="n">classes</span><span class="p">.</span><span class="n">waitForFixedUpdate</span><span class="p">))</span>
<span class="p">{</span>
    <span class="n">CallDelayed</span> <span class="p">(</span><span class="n">ContinueCoroutine</span><span class="p">,</span> <span class="n">m_Behaviour</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">F</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">F</span><span class="p">,</span> <span class="n">CleanupCoroutine</span><span class="p">,</span> <span class="n">DelayedCallManager</span><span class="o">::</span><span class="n">kRunFixedFrameRate</span><span class="p">);</span>
    <span class="k">return</span><span class="p">;</span>  
<span class="p">}</span>

<span class="c1">//yield return new WaitForEndOfFrame()</span>
<span class="k">if</span> <span class="p">(</span><span class="n">scripting_class_is_subclass_of</span> <span class="p">(</span><span class="n">waitClass</span><span class="p">,</span> <span class="n">classes</span><span class="p">.</span><span class="n">waitForEndOfFrame</span><span class="p">))</span>
<span class="p">{</span>
    <span class="n">CallDelayed</span> <span class="p">(</span><span class="n">ContinueCoroutine</span><span class="p">,</span> <span class="n">m_Behaviour</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">F</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">F</span><span class="p">,</span> <span class="n">CleanupCoroutine</span><span class="p">,</span> <span class="n">DelayedCallManager</span><span class="o">::</span><span class="n">kEndOfFrame</span><span class="p">);</span>
    <span class="k">return</span><span class="p">;</span>  
<span class="p">}</span>

<span class="c1">//yield return 另一个协程</span>
<span class="k">if</span> <span class="p">(</span><span class="n">scripting_class_is_subclass_of</span> <span class="p">(</span><span class="n">waitClass</span><span class="p">,</span> <span class="n">classes</span><span class="p">.</span><span class="n">coroutine</span><span class="p">))</span>
<span class="p">{</span>
    <span class="n">Coroutine</span><span class="o">*</span> <span class="n">waitForCoroutine</span><span class="p">;</span>
    <span class="p">...</span>
    <span class="k">if</span><span class="p">(</span><span class="n">waitForCoroutine</span><span class="o">-&gt;</span><span class="n">m_DoneRunning</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">ContinueCoroutine</span><span class="p">(</span><span class="n">m_Behavoir</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="p">...</span>
    <span class="k">return</span><span class="p">;</span>  
<span class="p">}</span>

<span class="c1">//yield return www</span>
<span class="k">if</span> <span class="p">(</span><span class="n">scripting_class_is_subclass_of</span> <span class="p">(</span><span class="n">waitClass</span><span class="p">,</span> <span class="n">classes</span><span class="p">.</span><span class="n">www</span><span class="p">))</span>
<span class="p">{</span>
    <span class="n">WWW</span><span class="o">*</span> <span class="n">wwwPtr</span><span class="p">;</span>
    <span class="k">if</span><span class="p">(</span><span class="n">wwwPtr</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">//WWW类型比较特殊它本身做了类似的处理,它提供了一个方法CallWhenDone,当它完成的时候直接回调Coroutine。</span>
        <span class="n">wwwPtr</span><span class="o">-&gt;</span><span class="n">CallWhenDone</span><span class="p">(</span><span class="n">ContinueCoroutine</span><span class="p">,</span> <span class="n">m_Behaviour</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span> <span class="n">CleanupCoroutine</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">return</span><span class="p">;</span>  
<span class="p">}</span>

}

void Coroutine::ContinueCoroutine (Object o, void userData)
{
Coroutine coroutine = (Coroutine)userData;
if((Object*)coroutine->m_Behaviour != o)
{

reutrn;
}
coroutine->Run();
}

CallDelayed.cpp(Unity底层)
//这个枚举型就是下面用到的mode
enum  {
    kRunFixedFrameRate = 1 << 0,
    kRunDynamicFrameRate = 1 << 1,
    kRunStartupFrame = 1 << 2,
    kWaitForNextFrame = 1 << 3,
    kAfterLoadingCompleted = 1 << 4,
    kEndOfFrame = 1 << 5
};

void CallDelayed (DelayedCall func, PPtr<Object> o, float time, void userData, float repeatRate, CleanupUserData* cleanup, int mode)
{
DelayedCallManager::Callback callback;

<span class="n">callback</span><span class="p">.</span><span class="n">time</span> <span class="o">=</span> <span class="n">time</span> <span class="o">+</span> <span class="n">GetCurTime</span> <span class="p">();</span>
<span class="n">callback</span><span class="p">.</span><span class="n">userData</span> <span class="o">=</span> <span class="n">userData</span><span class="p">;</span>
<span class="n">callback</span><span class="p">.</span><span class="n">call</span> <span class="o">=</span> <span class="n">func</span><span class="p">;</span>
<span class="n">callback</span><span class="p">.</span><span class="n">cleanup</span> <span class="o">=</span> <span class="n">cleanup</span><span class="p">;</span>
<span class="n">callback</span><span class="p">.</span><span class="n">object</span> <span class="o">=</span> <span class="n">o</span><span class="p">;</span>
<span class="n">callback</span><span class="p">.</span><span class="n">mode</span> <span class="o">=</span> <span class="n">mode</span><span class="p">;</span>
<span class="p">...</span>
    
<span class="c1">//将callback保存在DelayedCallManager的Callback List中</span>
<span class="n">GetDelayedCallManager</span> <span class="p">().</span><span class="n">m_CallObjects</span><span class="p">.</span><span class="n">insert</span> <span class="p">(</span><span class="n">callback</span><span class="p">);</span>

}

void DelayedCallManager::Update (int modeMask)
{
float time = GetCurTime();
Container::iterator i = m_CallObjects.begin ();

<span class="k">while</span> <span class="p">(</span><span class="n">i</span> <span class="o">!=</span>  <span class="n">m_CallObjects</span><span class="p">.</span><span class="n">end</span> <span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">i</span><span class="o">-&gt;</span><span class="n">time</span> <span class="o">&lt;=</span> <span class="n">time</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">m_NextIterator</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>	<span class="n">m_NextIterator</span><span class="o">++</span><span class="p">;</span>
    <span class="n">Callback</span> <span class="o">&amp;</span><span class="n">cb</span> <span class="o">=</span> <span class="k">const_cast</span><span class="o">&lt;</span><span class="n">Callback</span><span class="o">&amp;&gt;</span> <span class="p">(</span><span class="o">*</span><span class="n">i</span><span class="p">);</span>
    
    <span class="c1">// - 确保modeMask匹配</span>
    <span class="c1">// - 不执行那些在DelayedCallManager::Update中被添加进来的delayed calls</span>
    <span class="k">if</span><span class="p">((</span><span class="n">cb</span><span class="p">.</span><span class="n">mode</span> <span class="o">&amp;</span> <span class="n">modeMask</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">cb</span><span class="p">.</span><span class="n">timeStamp</span> <span class="o">!=</span> <span class="n">m_TimeStamp</span> <span class="o">&amp;&amp;</span> <span class="n">cb</span><span class="p">.</span><span class="n">frame</span> <span class="o">&lt;=</span> <span class="n">frame</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">void</span><span class="o">*</span> <span class="n">userData</span> <span class="o">=</span> <span class="n">cb</span><span class="p">.</span><span class="n">userData</span><span class="p">;</span>
        <span class="n">DelayedCall</span><span class="o">*</span> <span class="n">callback</span> <span class="o">=</span> <span class="n">cb</span><span class="p">.</span><span class="n">call</span><span class="p">;</span>
        
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">cb</span><span class="p">.</span><span class="n">repeat</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="err">从</span><span class="n">callback</span><span class="err">列表中移除即将被执行的</span><span class="n">callback</span><span class="p">;</span>
            <span class="n">callback</span> <span class="p">(</span><span class="n">o</span><span class="p">,</span> <span class="n">userData</span><span class="p">);</span>    <span class="c1">//执行callback</span>
            <span class="err">清除</span><span class="n">userData</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">else</span>
        <span class="p">{</span>
            <span class="c1">//增加时间后并重新插入callback列表中</span>
            <span class="n">cb</span><span class="p">.</span><span class="n">time</span> <span class="o">+=</span> <span class="n">cb</span><span class="p">.</span><span class="n">repeatRate</span><span class="p">;</span>
            <span class="p">...</span>
            <span class="n">m_CallObjects</span><span class="p">.</span><span class="n">insert</span> <span class="p">(</span><span class="n">cb</span><span class="p">);</span>
            
            <span class="err">从</span><span class="n">callback</span><span class="err">列表中移除即将被执行的</span><span class="n">callback</span><span class="p">;</span>
            <span class="n">callback</span> <span class="p">(</span><span class="n">o</span><span class="p">,</span> <span class="n">userData</span><span class="p">);</span>    <span class="c1">//执行callback</span>
        <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="n">i</span> <span class="o">=</span> <span class="n">m_NextIterator</span><span class="p">;</span>
<span class="p">}</span>

}

详细的流程分析:

  • C#层调用StartCoroutine方法,将IEnumerator对象(或者是用于创建IEnumerator对象的方法名字符串)传入C++层。
  • 通过mono的反射功能,找到IEnuerator上的moveNextcurrent两个方法,然后创建出一个对应的Coroutine对象,把两个方法传递给这个Coroutine对象。
  • 创建好之后这个Coroutine对象会保存在MonoBehaviour一个成员变量List中,这样使得MonoBehaviour具备StopCoroutine功能,StopCoroutine能够找到对应Coroutine并停止。
  • 调用这个Coroutine对象的Run方法。

  • Coroutine.Run中,然后调用一次MoveNext。如果MoveNext返回false,表示Coroutine执行结束,进入清理流程;如果返回true,表示Coroutine执行到了一句yield return处,这时就需要调用invocation(m_Current).Invoke取出yield return返回的对象monoWait,再根据monoWait的具体类型(null、WaitForSeconds、WaitForFixedUpdate等),将Coroutine对象保存到DelayedCallManager的callback列表m_CallObjects中。
  • 至此,Coroutine在当前帧的执行即结束。

  • 之后游戏运行过程中,游戏主循环的PlayerLoop方法会在每帧的不同时间点以不同的modeMask调用DelayedCallManager.Update方法,Update方法中会遍历callback列表中的Coroutine对象,如果某个Coroutine对象的monoWait的执行条件满足,则将其从callback列表中取出,执行这个Coroutine对象的Run方法,回到之前的执行流程中。

至此,Coroutine的整体流程已经分析完毕,实现原理已经很明朗了。


总结

  1. 协程只是看起来像多线程一样,其实还是在主线程上执行。
  2. 协程只是个伪异步,内部的死循环依旧会导致应用卡死。
  3. yield是C#的语法糖,和Unity没有关系。
  4. 避免使用字符串的版本开启一个协程,字符串的版本在运行时要用mono的反射做更多参数检查、函数查询工作,带来性能损失。
            <hr style="visibility: hidden;">

            <ul class="pager">
                
                <li class="previous">
                    <a href="/2020/04/27/%E5%88%A9%E7%94%A8Sqlite%E5%AE%8C%E6%88%90%E6%9C%AC%E5%9C%B0%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8/" data-toggle="tooltip" data-placement="top" title="利用 Sqlite 完成数据持久化">
                    Previous<br>
                    <span>利用 Sqlite 完成数据持久化</span>
                    </a>
                </li>
                
                
                <li class="next">
                    <a href="/2020/05/15/%E5%88%9D%E8%AF%86Lua/" data-toggle="tooltip" data-placement="top" title="初识 Lua">
                    Next<br>
                    <span>初识 Lua</span>
                    </a>
                </li>
                
            </ul>


            <!--Gitalk评论start  -->
            
            <!-- 引入Gitalk评论插件  -->
            <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
            <script src="https://unpkg.com/gitalk@latest/dist/gitalk.min.js"></script>
            <div id="gitalk-container"><div class="gt-container"><div class="gt-no-init"><p>未找到相关的 <a href="https://github.com/sun-wei-zhe/sun-wei-zhe.github.io/issues">Issues</a> 进行评论</p><p>请联系 @sun-wei-zhe 初始化创建</p><button class="gt-btn gt-btn-login"><span class="gt-btn-text">使用 GitHub 登录</span></button></div></div></div>
            <!-- 引入一个生产md5的js,用于对id值进行处理,防止其过长 -->
            <!-- Thank DF:https://github.com/NSDingFan/NSDingFan.github.io/issues/3#issuecomment-407496538 -->
            <script src="/js/md5.min.js"></script>
            <script type="text/javascript">
                var gitalk = new Gitalk({
                clientID: '3445cf762f02d6815bf6',
                clientSecret: '1cd55108178ef8d2a4d8a97ec9d5463c74a90561',
                repo: 'sun-wei-zhe.github.io',
                owner: 'sun-wei-zhe',
                admin: ['sun-wei-zhe'],
                distractionFreeMode: true,
                id: md5(location.pathname),
                });
                gitalk.render('gitalk-container');
            </script>
            
            <!-- Gitalk end -->

            

        </div>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值