C# Start例程/收益回报模式在Unity中究竟是如何工作的?

C# Start例程/收益回报模式在Unity中究竟是如何工作的?,c#,unity3d,coroutine,C#,Unity3d,Coroutine,我理解协同程序的原理。我知道如何让标准的startcroutin/yield return模式在C#in Unity中工作,例如,通过startcroutin调用一个返回IEnumerator的方法,并在该方法中执行一些操作,执行yield return new WaitForSeconds(1)等待一秒钟,然后执行其他操作 我的问题是:幕后到底发生了什么?startcroutine真正做什么?IEnumerator返回的是什么WaitForSeconds?startcroutine如何将控制权

我理解协同程序的原理。我知道如何让标准的
startcroutin
/
yield return
模式在C#in Unity中工作,例如,通过
startcroutin
调用一个返回
IEnumerator
的方法,并在该方法中执行一些操作,执行
yield return new WaitForSeconds(1)等待一秒钟,然后执行其他操作


我的问题是:幕后到底发生了什么?
startcroutine
真正做什么?
IEnumerator
返回的是什么
WaitForSeconds
startcroutine
如何将控制权返回到被调用方法的“其他内容”部分?所有这些是如何与Unity的并发模型相互作用的(在这个模型中,许多事情同时进行,而不使用协同路由)?

下面的第一个标题是对这个问题的直接回答。后面的两个标题对日常程序员更有用

协同程序的实现细节可能很枯燥 在和其他地方解释了协同程序。这里我将从实际的角度提供一些细节<代码>IEnumerator
收益率
等是用于统一中的不同目的的

简单地说,
IEnumerator
声称拥有一组可以逐个请求的值,有点像
列表。在C#中,带有返回
IEnumerator
的签名的函数不必实际创建并返回一个,但可以让C#提供一个隐式
IEnumerator
。然后,该函数可以通过
yield return
语句,在将来以惰性方式提供返回的
IEnumerator
的内容。每次调用方从该隐式
IEnumerator
请求另一个值时,该函数将一直执行到下一个
yield return
语句,该语句提供下一个值。作为此操作的副产品,函数将暂停,直到请求下一个值

在Unity中,我们不使用这些函数来提供未来的值,而是利用函数暂停的事实。由于这种利用,关于Unity中的协同程序的很多事情都没有意义(IEnumerator
与任何事情有什么关系?什么是
产生的
?为什么
新的WaitForSeconds(3)
?等等)。“在引擎盖下”发生的情况是,
startcroutine()
使用您通过IEnumerator提供的值来决定何时请求下一个值,这决定了您的协同程序何时将再次取消暂停

您的Unity游戏是单线程(*) 协程是而不是线程。有一个统一的主循环,您编写的所有函数都被同一个主线程按顺序调用。您可以通过放置
while(true)来验证这一点代码。它将冻结整个事件,甚至是Unity编辑器。这是所有东西都在一个主线程中运行的证据。凯在上述评论中提到的这一点也是一个很好的资源

(*)Unity从一个线程调用函数。因此,除非您自己创建线程,否则您编写的代码是单线程的。当然,Unity会使用其他线程,如果愿意,您可以自己创建线程

游戏程序员协作程序的实用描述 基本上,当您调用
start例程(MyCoroutine())
时,它与对
MyCoroutine()
的常规函数调用完全相同,直到第一个
返回X
,其中
X
类似于
null
新的WaitForSeconds(3)
start例程(另一个协同例程())
中断
等。这是当它开始与函数不同时。Unity将该函数“暂停”在该
收益返回X
行,继续处理其他事务,并且一些帧经过,当时间再次到来时,Unity将在该行之后恢复该函数。它记住函数中所有局部变量的值。例如,通过这种方式,可以使
for
循环每两秒循环一次


Unity何时恢复协同程序取决于
X
中的
收益率X
。例如,如果使用
返回新的WaitForSeconds(3),3秒后恢复。如果使用了
yield return startcroutine(anothercorroutine())
,则在
anothercorroutine()
完成后,它将恢复,这使您能够及时嵌套行为。如果只使用
返回null,它将在下一帧恢复

我最近对此进行了深入研究,在这里写了一篇文章,这篇文章揭示了内部结构(有密集的代码示例)、底层接口以及如何将其用于协同路由

为此目的使用集合枚举器对我来说仍然有点奇怪。这与枚举器的设计目的正好相反。枚举数的点是每次访问时返回的值,而协程的点是值返回之间的代码。在这个上下文中,实际返回的值是没有意义的

经常引用的链接已失效。由于评论和答案中提到了这一点,我将在这里发布文章的内容。此内容来自


详细介绍Unity3D协同程序

游戏中的许多过程都发生在多帧的过程中。你有“密集”的进程,比如寻路,每一帧都很努力,但是会被分割到多个帧,这样就不会对帧率造成太大的影响。你有一些“稀疏”的过程,比如游戏触发,它们在大多数帧中都不起作用,但偶尔会被要求做一些关键的工作。这两者之间有各种各样的过程

无论何时,当您创建一个将在多个帧上进行的过程时–没有multith
function LongComputation()
{
    while(someCondition)
    {
        /* Do a chunk of work */

        // Pause here and carry on next frame
        yield;
    }
}
IEnumerator LongComputation()
{
    while(someCondition)
    {
        /* Do a chunk of work */

        // Pause here and carry on next frame
        yield return null;
    }
}
IEnumerator TellMeASecret()
{
  PlayAnimation("LeanInConspiratorially");
  while(playingAnimation)
    yield return null;

  Say("I stole the cookie from the cookie jar!");
  while(speaking)
    yield return null;

  PlayAnimation("LeanOutRelieved");
  while(playingAnimation)
    yield return null;
}
IEnumerator e = TellMeASecret();
while(e.MoveNext()) { }
IEnumerator e = TellMeASecret();
while(e.MoveNext()) 
{ 
  // If they press 'Escape', skip the cutscene
  if(Input.GetKeyDown(KeyCode.Escape)) { break; }
}
List<IEnumerator> unblockedCoroutines;
List<IEnumerator> shouldRunNextFrame;
List<IEnumerator> shouldRunAtEndOfFrame;
SortedList<float, IEnumerator> shouldRunAfterTimes;

foreach(IEnumerator coroutine in unblockedCoroutines)
{
    if(!coroutine.MoveNext())
        // This coroutine has finished
        continue;

    if(!coroutine.Current is YieldInstruction)
    {
        // This coroutine yielded null, or some other value we don't understand; run it next frame.
        shouldRunNextFrame.Add(coroutine);
        continue;
    }

    if(coroutine.Current is WaitForSeconds)
    {
        WaitForSeconds wait = (WaitForSeconds)coroutine.Current;
        shouldRunAfterTimes.Add(Time.time + wait.duration, coroutine);
    }
    else if(coroutine.Current is WaitForEndOfFrame)
    {
        shouldRunAtEndOfFrame.Add(coroutine);
    }
    else /* similar stuff for other YieldInstruction subtypes */
}

unblockedCoroutines = shouldRunNextFrame;
YieldInstruction y;

if(something)
 y = null;
else if(somethingElse)
 y = new WaitForEndOfFrame();
else
 y = new WaitForSeconds(1.0f);

yield return y;
IEnumerator DoSomething()
{
  /* ... */
}

IEnumerator DoSomethingUnlessInterrupted()
{
  IEnumerator e = DoSomething();
  bool interrupted = false;
  while(!interrupted)
  {
    e.MoveNext();
    yield return e.Current;
    interrupted = HasBeenInterrupted();
  }
}
IEnumerator UntilTrueCoroutine(Func fn)
{
   while(!fn()) yield return null;
}

Coroutine UntilTrue(Func fn)
{
  return StartCoroutine(UntilTrueCoroutine(fn));
}

IEnumerator SomeTask()
{
  /* ... */
  yield return UntilTrue(() => _lives < 3);
  /* ... */
}
void Update()
 {
 this happens every frame,
 you want Unity to do something of "yours" in each of the frame,
 put it in here
 }

...in a coroutine...
 while(true)
 {
 this happens every frame.
 you want Unity to do something of "yours" in each of the frame,
 put it in here
 yield return null;
 }
  void func()
  IEnumerator func()
  Time.deltaTime
  yield return new WaitForSeconds();
    public IEnumerator GameOver()
{
    while (true)
    {
        _gameOver.text = "GAME OVER";
        yield return new WaitForSeconds(Random.Range(1.0f, 3.5f));
        _gameOver.text = "";
        yield return new WaitForSeconds(Random.Range(0.1f, 0.8f));
    }
}
    public void UpdateLives(int currentlives)
{
    if (currentlives < 1)
    {
        _gameOver.gameObject.SetActive(true);
        StartCoroutine(GameOver());
    }
}