25

我对协程(在 Unity3D 和其他地方)如何工作感到困惑和好奇。协程是一个新线程吗?他们说Unity 的文档:

协程是一个可以暂停执行(yield)直到给定的 YieldInstruction 完成的函数。

他们在这里有 C# 示例:

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    void Start() {
        print("Starting " + Time.time);
        StartCoroutine(WaitAndPrint(2.0F));
        print("Before WaitAndPrint Finishes " + Time.time);
    }
    IEnumerator WaitAndPrint(float waitTime) {
        yield return new WaitForSeconds(waitTime);
        print("WaitAndPrint " + Time.time);
    }
}

我对这个例子有很多疑问:

  1. 在上面的例子中,哪一行是协程?是WaitAndPrint()协程吗?是WaitForSeconds()协程吗?

  2. 在这一行:yield return new WaitForSeconds(waitTime);,为什么yieldreturn都存在?我在Unity 文档中读到“yield 语句是一种特殊的返回,它确保函数将在下次调用它时从 yield 语句之后的行继续执行。” 如果yield是特殊的returnreturn这里在做什么?

  3. 为什么我们必须返回一个IEnumerator

  4. 是否StartCoroutine开始一个新线程?

  5. WaitAndPrint()在上面的例子中被调用了多少次?真的yield return new WaitForSeconds(waitTime);回来了吗?如果是,那么我猜WaitAndPrint()在上面的代码中被调用了两次。我猜是多次StartCoroutine()打电话。WaitAndPrint()但是,我看到另一个 Unity 文档说:“可以使用 yield 语句随时暂停协程的执行。yield 返回值指定何时恢复协程。” 这些话让我觉得WaitAndPrint()其实还没有回来;它只是暂停了;它正在等待WaitForSeconds()返回。如果是这种情况,那么在上面的代码WaitAndPrint()中只调用了一次,并且StartCoroutine只负责启动函数,而不是多次调用它。

4

1 回答 1

26

协程是一种非常强大的技术,用于模拟 .net4.5 中的 async/await 支持的各种功能,但在早期版本中 (c# >= v2.0)。

Microsoft CCR(阅读)也采用(雇用?)这种方法。

让我们解决一件事。yield单独是无效的,并且总是跟在returnor之后break

考虑一个标准的 IEnumerator(它不会产生流控制消息)。

IEnumerator YieldMeSomeStuff()
{
    yield "hello";
    Console.WriteLine("foo!");
    yield "world";
}

现在:

IEnumerator e = YieldMeSomeStuff();
while(e.MoveNext())
{
    Console.WriteLine(e.Current);
}

输出是什么?

你好
呸!
世界

请注意,我们第二次调用MoveNext,在 Enumerator 产生“世界”之前,一些代码在 Enumerator 中运行。这意味着在 Enumerator 中,我们可以编写执行代码,直到遇到yield return语句,然后简单地暂停,直到有人调用MoveNext(方便地整齐地捕获所有状态/变量,所以我们可以从中断的地方继续)。MoveNext调用之后,yield return语句之后的下一段代码可以运行,直到到达另一个yield return。所以我们现在可以通过调用枚举器来控制yield return语句之间代码的执行。MoveNext

现在,假设我们的枚举器不是产生字符串,而是产生一条消息,告诉调用者MoveNext请在再次调用之前等待 x (waitTime) 秒MoveNext。调用者被写入以“理解”各种消息。这些消息将始终遵循“请等待某事发生后再调用MoveNext的内容。

现在我们有了一种强大的暂停和重新启动代码的方法,该方法需要满足其他条件才能继续执行,而无需将该功能写入另一个方法,例如在没有协程的情况下执行异步操作。如果没有协程,您将被迫传递一个可怕的异步状态对象,您需要手动组装该对象以捕获一个方法结束和另一个方法开始之间的状态(经过一些异步操作)。协程消除了这一点,因为范围被保留(通过编译器魔法),因此您的局部变量在长期存在的异步内容中持续存在。

StartCoroutine简单地开始整个过程​​。它调用MoveNext枚举器...一些代码在枚举器中运行...枚举器产生一条控制消息,通知代码StartCoroutine何时MoveNext再次调用。这不需要在新线程中发生,但在多线程场景中可以很方便,因为我们可以MoveNext从不同的线程调用并控制工作在哪里完成。

于 2013-06-06T01:15:49.857 回答