Unity - coroutines 协程揭秘

孙广东 2015.6.16


理解UNity协同系统将帮助您更有效地使用它。

UNity协同程序可能似乎有点奇怪,很少有人知道他们是如何工作的。

了解IEnumerator

你能猜到,协同工作与 IEnumerator接口和 CLR 枚举实现有关,所以我们首先应该明白这些东西是如何工作。最初, IEnumerator和IEnumerable应该处理的集合的Item,如lists or arrays。想象一下,一个字符串,也可以是一个字符数组。循环访问集合的常用方法是使用foreach语句:

foreach(char c in "coroutine")
  Debug.Log(c);

这整洁的声明和IEnumerable接口,只知道如何提供IEnumerator一起工作。enumerator 告诉小 “story” 的如何遍历当前集合 (它可以提供当前元素,并知道如何移动到下一个)。总体来看,它看上去有点像这样:


class Enumerable // Typically implements IEnumerable or IEnumerable<T>
{
  public Enumerator GetEnumerator() {...}
}

class Enumerator // Typically implements IEnumerator or IEnumerator<T>
{
  public IteratorVariableType Current { get {...} }
  public bool MoveNext() {...}
}

这么说,这是foreach语句, 它在编译时:

using (var enumerator = "coroutine".GetEnumerator())
  while (enumerator.MoveNext())
  {
  var element = enumerator.Current;
  Console.WriteLine (element);
  }

IEnumerator也实现IDisposable,所以它是完全合法using语句内使用它。

迭代器 Iterators

这是关键的话题。我们实际上几乎就是协同程序本身。在我们的示例中,foreach 语句是枚举的enumeration的一个消费者,而迭代器是enumeration的生产者看看代码:

 ...
void Start()
{
  foreach(int fibNum in Fibs(6))
    Debug.Log(fibNum);
}

IEnumerable<int> Fibs(int fibCount)
{
   for(int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
   {
		yield return prevFib;
	 
		int newFib = prevFib + curFib;
		prevFib = curFib;
		curFib = newFib;
   }
}


看看线 # 12。yield语句, IEnumerable返回类型,以及是什么使得它都如此特别。return语句给你一个 该方法返回yield return 集合的下一个元素,你问此方法以yield 从enumerator 的值。每次遇到了yield ,控制返回到调用方,和它返回到 calee,在下一次它捡起并继续。编译器实际上将这种方法 (Fibs(...)) 转换成拼接这个yield return的逻辑, 纳入MoveNext方法和Current属性的私有类。当调用此方法时,你只是 实例化编译器编写的类 和无代码实际运行! 只有当你开始枚举此“on-the-fly”集合时,将运行您的代码!

迭代器可以有一个或多个yield语句并且应该返回以下的接口来绕过编译器错误:

  • System.Collections.IEnumerable
  • System.Collections.Generic.IEnumerable<T>
  • System.Collections.IEnumerator
  • System.Collections.Generic.IEnumerator<T>

也是yield break的语句,它允许终止迭代,所以没有更多的元素从enumerator返回。


回到协同程序

让我们看看此enumerator 逻辑如何转化为Unity协同程序。你可能已经注意到,协同程序通常yield return不同时间跨度或null, 如果代码需要继续在下一帧。这是因为Unity需要这些返回的值,以知道何时调用MoveNext()方法!  当你写你协同程序。例如以这段代码:

void Start()
{
    WaitForSeconds delay = new WaitForSeconds(1f);
    StartCoroutine(TestCoroutine(delay));
}

IEnumerator TestCoroutine(WaitForSeconds delay)
{
    while (true)
    {
        Debug.Log("tick");
        yield return delay;
    }
}

正如你所看到的还有一个yield return 语句返回传递的 WaitForSeconds对象,用于延迟。“tick” 消息将立即写入控制台,和在那之后的每一秒。尝试移动Debug.Log(..)调用后yield return语句,看看会发生什么。

让我们看看在调用TestCoroutine方法时,会发生什么。


void Start()
{
    WaitForSeconds delay = new WaitForSeconds(1f);
    TestCoroutine(delay);
}

似乎没有什么发生,但我们实际上在堆中创建enumerator 对象。你也可以做出这样的事情:


void Start()
{
	IEnumerator rator = TestCoroutine(new WaitForSeconds(1f));
	for (int i = 0; i < 5; i++)
	{
             rator.MoveNext();
	}
}

但无论如何它们确实很酷。

时刻牢记

  • 创建协同是创建一个迭代器,通过虚构的collection of items 的过程。
  • 迭代器返回值用于确定MoveNext方法到下一次调用的时间。
  • 每次StartCoroutine被调用, 协同程序将分配在堆中分配一个 enumerator 对象。

希望这将帮助你更有效。你也可以打动你的同事与自定义协同执行!


最后,贴上一个自定义实现的协程程序:http://wiki.unity3d.com/index.php?title=CoroutineScheduler






  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值