我对coroutines(在Unity3D和其他地方)如何运作感到困惑和好奇。 coroutine是新线程吗? Unity的documentation他们说:
协程是一个可以暂停执行(yield)的函数,直到给定的YieldInstruction结束。
他们有C#示例here:
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);
}
}
我对这个例子有很多疑问:
在上面的例子中,哪一行是协程?是WaitAndPrint()
是一个协程吗? WaitForSeconds()
是一个协程吗?
在这一行:yield return new WaitForSeconds(waitTime);
,为什么yield
和return
都存在?我在Unity documentation中读到“yield语句是一种特殊的返回,它确保函数将在下次调用yield语句后继续执行。”如果yield
是特殊的return
,return
在这做什么?
为什么我们必须返回IEnumerator
?
StartCoroutine
是否会启动新主题?
在上面的示例中调用了WaitAndPrint()
多少次? yield return new WaitForSeconds(waitTime);
真的回来了吗?如果是,那么我猜在上面的代码中调用了两次WaitAndPrint()
。我猜StartCoroutine()
多次调用WaitAndPrint()
。但是,我看到another Unity documentation说:“使用yield语句可以在任何时候暂停执行协程。yield return value指定何时恢复协程。”这些话让我觉得WaitAndPrint()
实际上没有回来;它只是暂停了;等待WaitForSeconds()
返回。如果是这种情况,那么在上面的代码WaitAndPrint()
中只调用一次,而StartCoroutine
只负责启动该函数,而不是多次调用它。
答案 0 :(得分:21)
协同程序是一种非常强大的技术,用于模拟.net4.5中async / await支持的各种功能,但在早期版本中(c#> = v2.0)。
Microsoft CCR(读一读)也采用(雇用?)这种方法。
让我们放开一件事。仅yield
无效,并且始终跟有return
或break
。
考虑一个标准的IEnumerator(它不会产生流控制消息)。
IEnumerator YieldMeSomeStuff()
{
yield "hello";
Console.WriteLine("foo!");
yield "world";
}
现在:
IEnumerator e = YieldMeSomeStuff();
while(e.MoveNext())
{
Console.WriteLine(e.Current);
}
输出是什么?
hello foo! world
注意我们第二次调用MoveNext
,在枚举器产生“世界”之前,在枚举器中运行了一些代码。这意味着在枚举器中,我们可以编写执行代码直到它到达yield return
语句,然后暂停直到有人调用MoveNext
(方便地使用整齐捕获的所有状态/变量,所以我们可以拿起我们离开的地方)。在MoveNext
调用之后,yield return
语句之后的下一位代码可以运行,直到达到另一个yield return
。因此,我们现在可以通过yield return
调用枚举器来控制MoveNext
语句之间的代码执行。
现在,我们的枚举器不是产生字符串,而是发出一条消息,告诉调用者MoveNext
,“在你调用{{1}之前请等待x(waitTime)秒再次“。写入调用者是为了“理解”各种消息。这些消息将始终沿着的路线“请等待再次呼叫MoveNext
之前发生此类事件。
现在我们有一个强大的暂停和重启代码的方法,需要满足其他条件才能继续,而不必将该功能写入另一个方法,比如在没有协同程序的情况下执行异步操作。如果没有协同程序,你就会强行传递一个可怕的异步状态对象,你需要手动组装以捕获一个方法结束和另一个异步之后的另一个方法的启动之间的状态。协同程序消除了这种情况,因为范围被保留(通过编译器魔术),因此您的本地变量会持久存在于长期存在的异步内容中。
MoveNext
只是启动整个过程。它在枚举器上调用StartCoroutine
...某些代码在枚举器中运行...枚举器产生一条控制消息,通知MoveNext
中的代码何时再次调用StartCoroutine
。这不需要在新的Thread中发生,但在多线程场景中可以很方便,因为我们可以从不同的线程调用MoveNext
并控制工作的完成位置。