Unity - coroutines 协程揭秘

发表于2017-10-16
评论0 1.3k浏览

下面给大家揭秘下Unity  coroutines 协同系统,主要是帮助大家去理解UNity协同系统并让大家可以更有效地使用它。UNity协同程序可能似乎有点奇怪,很少有人知道他们是如何工作的,所以建议那些不了解UNity协同程序的一定要看看。


了解IEnumerator

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

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

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

  1. class Enumerable // Typically implements IEnumerable or IEnumerable<T>  
  2. {  
  3.   public Enumerator GetEnumerator() {...}  
  4. }  
  5.   
  6. class Enumerator // Typically implements IEnumerator or IEnumerator<T>  
  7. {  
  8.   public IteratorVariableType Current { get {...} }  
  9.   public bool MoveNext() {...}  
  10. }  

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

  1. using (var enumerator = "coroutine".GetEnumerator())  
  2.   while (enumerator.MoveNext())  
  3.   {  
  4.   var element = enumerator.Current;  
  5.   Console.WriteLine (element);  
  6.   }  


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

迭代器 Iterators

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

  1.  ...  
  2. void Start()  
  3. {  
  4.   foreach(int fibNum in Fibs(6))  
  5.     Debug.Log(fibNum);  
  6. }  
  7.   
  8. IEnumerable<int> Fibs(int fibCount)  
  9. {  
  10.    for(int i = 0, prevFib = 1, curFib = 1; i < fibCount; i )  
  11.    {  
  12.         yield return prevFib;  
  13.        
  14.         int newFib = prevFib   curFib;  
  15.         prevFib = curFib;  
  16.         curFib = newFib;  
  17.    }  
  18. }  


看看线 # 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()方法!  当你写你协同程序。例如以这段代码:

  1. void Start()  
  2. {  
  3.     WaitForSeconds delay = new WaitForSeconds(1f);  
  4.     StartCoroutine(TestCoroutine(delay));  
  5. }  
  6.   
  7. IEnumerator TestCoroutine(WaitForSeconds delay)  
  8. {  
  9.     while (true)  
  10.     {  
  11.         Debug.Log("tick");  
  12.         yield return delay;  
  13.     }  
  14. }  

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


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

  1. void Start()  
  2. {  
  3.     WaitForSeconds delay = new WaitForSeconds(1f);  
  4.     TestCoroutine(delay);  
  5. }  

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

  1. void Start()  
  2. {  
  3.     IEnumerator rator = TestCoroutine(new WaitForSeconds(1f));  
  4.     for (int i = 0; i < 5; i )  
  5.     {  
  6.              rator.MoveNext();  
  7.     }  
  8. }  

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

时刻牢记

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

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


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


http://blog.csdn.net/u010019717/article/details/46493743

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

标签: