Unity協程(Coroutine)原理深刻剖析再續

Unity協程(Coroutine)原理深刻剖析再續html

 

 By D.S.Qiuios

尊重他人的勞動,支持原創,轉載請註明出處:http.dsqiu.iteye.comweb

        

        前面已經介紹過對協程(Coroutine)的認識和理解,主要講到了Unity引擎在執行協程(Coroutine)的原理(Unity協程(Coroutine)原理深刻剖析)和對協程(Coroutine)狀態的控制(Unity協程(Coroutine)管理類——TaskManager工具分享),到這使用Coroutine的疑問就沒有了,可是D.S.Qiu仍是有點沒嚼爛,因此以爲頗有必要再續。express

        本文主要分爲三部分:api

               1)yield return, IEnumerator  和 Unity StartCoroutine 的關係和理解安全

               2)Cortoutine 擴展——Extending Coroutines: Return Values and Error Handling多線程

               3)Cortountine Locking併發

 

         總之,引用③的一句話:Coroutines – More than you want to know.app

         

1)yield return, IEnumerator  和 Unity StartCoroutine 的關係和理解函數

          yield 和 IEnumerator都是C#的東西,前者是一個關鍵字,後者是枚舉類的接口。對於IEnumerator 只引用②對 IEnumerable與IEnumerator區別 的論述:

先貼出 IEnumerable 和 IEnumerator的定義:

C#代碼   收藏代碼
  1. public interface IEnumerable  
  2. {  
  3.     IEnumerator GetEnumerator();  
  4. }  
  5.    
  6. public interface IEnumerator  
  7. {  
  8.     bool MoveNext();  
  9.     void Reset();  
  10.    
  11.     Object Current { get; }  
  12. }  

 IEnumerable和IEnumerator有什麼區別?這是一個很讓人困惑的問題(在不少forum裏都看到有人在問這個問題)。研究了半天,獲得如下幾點認識:

         一、一個Collection要支持foreach方式的遍歷,必須實現IEnumerable接口(亦即,必須以某種方式返回IEnumerator object)。

         二、IEnumerator object具體實現了iterator(經過MoveNext(),Reset(),Current)。

         三、從這兩個接口的用詞選擇上,也能夠看出其不一樣:IEnumerable是一個聲明式的接口,聲明實現該接口的class是「可枚舉(enumerable)」的,但並無說明如何實現枚舉器(iterator);IEnumerator是一個實現式的接口,IEnumerator object就是一個iterator。

         四、IEnumerable和IEnumerator經過IEnumerable的GetEnumerator()方法創建了鏈接,client能夠經過IEnumerable的GetEnumerator()獲得IEnumerator object,在這個意義上,將GetEnumerator()看做IEnumerator object的factory method也何嘗不可。

 

IEnumerator  是全部枚舉數的基接口。   

         枚舉數只容許讀取集合中的數據。枚舉數沒法用於修改基礎集合。   

         最初,枚舉數被定位於集合中第一個元素的前面。Reset   也將枚舉數返回到此位置。在此位置,調用   Current   會引起異常。所以,在讀取   Current   的值以前,必須調用   MoveNext   將枚舉數提早到集合的第一個元素。   

         在調用   MoveNext   或   Reset   以前,Current   返回同一對象。MoveNext   將   Current   設置爲下一個元素。   

         在傳遞到集合的末尾以後,枚舉數放在集合中最後一個元素後面,且調用   MoveNext   會返回   false。若是最後一次調用   MoveNext   返回   false,則調用   Current   會引起異常。若要再次將   Current   設置爲集合的第一個元素,能夠調用   Reset,而後再調用   MoveNext。   

         只要集合保持不變,枚舉數就將保持有效。若是對集合進行了更改(例如添加、修改或刪除元素),則該枚舉數將失效且不可恢復,而且下一次對   MoveNext   或   Reset   的調用將引起   InvalidOperationException。若是在   MoveNext   和   Current   之間修改集合,那麼即便枚舉數已經無效,Current   也將返回它所設置成的元素。   

         枚舉數沒有對集合的獨佔訪問權;所以,枚舉一個集合在本質上不是一個線程安全的過程。甚至在對集合進行同步處理時,其餘線程仍能夠修改該集合,這會致使枚舉數引起異常。若要在枚舉過程當中保證線程安全,能夠在整個枚舉過程當中鎖定集合,或者捕捉因爲其餘線程進行的更改而引起的異常。

 

Yield關鍵字

在迭代器塊中用於向枚舉數對象提供值或發出迭代結束信號。它的形式爲下列之一⑥:

  yield return <expression_r>;

  yield break;

備註 :

  計算表達式並以枚舉數對象值的形式返回;expression_r 必須能夠隱式轉換爲迭代器的 yield 類型。

  yield 語句只能出如今 iterator 塊中,該塊可用做方法、運算符或訪問器的體。這類方法、運算符或訪問器的體受如下約束的控制:

  不容許不安全塊。

  方法、運算符或訪問器的參數不能是 ref 或 out。

  yield 語句不能出如今匿名方法中。

  當和 expression_r 一塊兒使用時,yield return 語句不能出如今 catch 塊中或含有一個或多個 catch 子句的 try 塊中。

 

  yield return 提供了迭代器一個比較重要的功能,即取到一個數據後立刻返回該數據,不須要所有數據裝入數列完畢,這樣有效提升了遍歷效率。

 

Unity StartCoroutine

      Unity使用 StartCoroutine(routine: IEnumerator): Coroutine 啓動協程,參數必須是 IEnumerator 對象。那麼Unity在背後作什麼神奇的處理呢?

      StartCoroutine函數的參數我通常都是經過傳入一個返回值爲 IEnumerator的函數獲得的:

C#代碼   收藏代碼
  1. IEnumerator WaitAndPrint(float waitTime) {  
  2.     yield return new WaitForSeconds(waitTime);  
  3.     print("WaitAndPrint " + Time.time);  
  4. }  

       在函數內使用前面介紹 yield 關鍵字返回 IEnumerator 對象,Unity 中實現了 YieldInstruction 做爲 yield 返回的基類,有 Cortoutine, WaitForSecondes, WaitForEndOfFrame, WaitForFixedUpdate, WWW 幾個子類實現。StartCoroutine 將 傳入的 IEnumerator 封裝爲 Coroutine 返回,引擎會對 Corountines 存儲和檢查 IEnumerator 的 Current值。

 

③枚舉了 WWW ,WaitForSeconds , null 和 WaitForEndOfFrame 檢查 Current值在MonoBebaviour生存週期的時間(沒有WaitForFixedUpdate ,D.S.Qiu猜想是其做者成文是Unity引擎尚未提供這個實現):

 

       WWW - after Updates happen for all game objects; check the isDone flag. If true, call the IEnumerator's MoveNext() function;

       WaitForSeconds - after Updates happen for all game objects; check if the time has elapsed, if it has, call MoveNext();

       null or some unknown value - after Updates happen for all game objects; Call MoveNext();

       WaitForEndOfFrame - after Render happens for all cameras; Call MoveNext().

 

若是最後一個 yield return 的 IEnumerator 已經迭代到最後一個是,MoveNext 就會 返回 false 。這時,Unity就會將這個 IEnumerator 從 cortoutines list 中移除。

 

       因此很容易一個出現的誤解:協程 Coroutines 並非並行的,它和你的其餘代碼都運行在同一個線程中,因此纔會在Update 和 Coroutine中使用 同一個值時纔會變得線程安全。這就是Unity對線程安全的解決策略——直接不使用線程,最近Unity 5 將要發佈說的很熱,看到就有徹底多線程的支持,不知道是怎麼實現的,從技術的角度,仍是很期待的哈。

 

       總結下: 在協程方法中使用 yield return 其實就是爲了返回 IEnumerator對象,只有當這個對象的 MoveNext() 返回 false 時,即該 IEnumertator 的 Current 已經迭代到最後一個元素了,纔會執行 yield return 後面的語句。也就是說, yield return 被會「翻譯」爲一個 IEnmerator 對象,要想深刻了解這方面的更多細節,能夠猛擊⑤查看。

       根據⑤ C# in depth 的理解——C# 編譯器會生成一個 IEnumerator 對象,這個對象實現的 MoveNext() 包含函數內全部 yield return 的處理,這裏僅附上一個例子:

C#代碼   收藏代碼
  1. using System;  
  2. using System.Collections;  
  3.   
  4. class Test  
  5. {  
  6.     static IEnumerator GetCounter()  
  7.     {  
  8.         for (int count = 0; count < 10; count++)  
  9.         {  
  10.             yield return count;  
  11.         }  
  12.     }  
  13. }  

 C#編譯器對應生成:

Cpp代碼   收藏代碼
  1. internal class Test  
  2. {  
  3.     // Note how this doesn't execute any of our original code  
  4.     private static IEnumerator GetCounter()  
  5.     {  
  6.         return new <GetCounter>d__0(0);  
  7.     }  
  8.   
  9.     // Nested type automatically created by the compiler to implement the iterator  
  10.     [CompilerGenerated]  
  11.     private sealed class <GetCounter>d__0 : IEnumerator<object>, IEnumerator, IDisposable  
  12.     {  
  13.         // Fields: there'll always be a "state" and "current", but the "count"  
  14.         // comes from the local variable in our iterator block.  
  15.         private int <>1__state;  
  16.         private object <>2__current;  
  17.         public int <count>5__1;  
  18.   
  19.         [DebuggerHidden]  
  20.         public <GetCounter>d__0(int <>1__state)  
  21.         {  
  22.             this.<>1__state = <>1__state;  
  23.         }  
  24.   
  25.         // Almost all of the real work happens here  
  26.         private bool MoveNext()  
  27.         {  
  28.             switch (this.<>1__state)  
  29.             {  
  30.                 case 0:  
  31.                     this.<>1__state = -1;  
  32.                     this.<count>5__1 = 0;  
  33.                     while (this.<count>5__1 < 10)        //這裏針對循環處理  
  34.                     {  
  35.                         this.<>2__current = this.<count>5__1;  
  36.                         this.<>1__state = 1;  
  37.                         return true;  
  38.                     Label_004B:  
  39.                         this.<>1__state = -1;  
  40.                         this.<count>5__1++;  
  41.                     }  
  42.                     break;  
  43.   
  44.                 case 1:  
  45.                     goto Label_004B;  
  46.             }  
  47.             return false;  
  48.         }  
  49.   
  50.         [DebuggerHidden]  
  51.         void IEnumerator.Reset()  
  52.         {  
  53.             throw new NotSupportedException();  
  54.         }  
  55.   
  56.         void IDisposable.Dispose()  
  57.         {  
  58.         }  
  59.   
  60.         object IEnumerator<object>.Current  
  61.         {  
  62.             [DebuggerHidden]  
  63.             get  
  64.             {  
  65.                 return this.<>2__current;  
  66.             }  
  67.         }  
  68.   
  69.         object IEnumerator.Current  
  70.         {  
  71.             [DebuggerHidden]  
  72.             get  
  73.             {  
  74.                 return this.<>2__current;  
  75.             }  
  76.         }  
  77.     }  
  78. }  

       從上面的C#實現能夠知道:函數內有多少個 yield return 在對應的 MoveNext() 就會返回多少次 true (不包含嵌套)。另外很是重要的一點的是:同一個函數內的其餘代碼(不是 yield return 語句)會被移到 MoveNext 中去,也就是說,每次 MoveNext 都會順帶執行同一個函數中 yield return 以前,以後 和兩個 yield return 之間的代碼。

       對於Unity 引擎的 YieldInstruction 實現,其實就能夠看着一個 函數體,這個函數體每幀會實現去 check MoveNext 是否返回 false 。 例如:

C#代碼   收藏代碼
  1. yield retrun new WaitForSeconds(2f);  

       上面這行代碼的僞代碼實現:

C#代碼   收藏代碼
  1. private float elapsedTime;  
  2. private float time;  
  3.   
  4. private void MoveNext()  
  5. {  
  6.         elapesedTime += Time.deltaTime;  
  7.           
  8.         if(time <= elapsedTime)  
  9.                 return false;  
  10.         else return true;  
  11.   
  12. }  

                                                                                                                                                                       增補於: 2014年04月22日 8:00

 

 2)Cortoutine 擴展——Extending Coroutines: Return Values and Error Handling

        不知道大家調用 StartCortoutine 的時候有沒有注意到 StartCortoutine 返回了 YieldInstruction 的子類 Cortoutine 對象,這個返回除了嵌套使用 StartCortoutine 在 yiled retrun StartCortoutine 有用到,其餘狀況機會就沒有考慮它的存在,反正D.S.Qiu是這樣的,一直認爲物「極」所用,因此每次調用 StartCortoutine 都很糾結,好吧,有點強迫症。

       Unity引擎講 StartCoroutine 傳入的參數 IEnumerator 封裝爲一個 Coroutine 對象中,而 Coroutine 對象其實也是 IEnumerator 枚舉對象。yield return 的 IEnumerator 對象都存儲在這個 Coroutine 中,只有當上一個yield return 的 IEnumerator 迭代完成,纔會運行下一個。這個在猜想下Unity底層對Cortountine 的統一管理(也就是上面說的檢查 Current 值):Unity底層應該有一個 正在運行的 Cortoutine 的 list 而後在每幀的不一樣時間去 Check。

 

        仍是迴歸到主題,上面介紹 yield 關鍵字有說不容許不安全塊,也就是說不能出如今 try catch 塊中,就不能在 yield return 執行是進行錯誤檢查。③利用 StartCortoutine 返回值 Cortoutine 獲得了當前的 Current 值和進行錯誤捕獲處理。

先定義封裝包裹返回值和錯誤信息的類:

C#代碼   收藏代碼
  1. public class Coroutine<T>{  
  2. public T Value {  
  3.     get{  
  4.         if(e != null){  
  5.             throw e;  
  6.         }  
  7.         return returnVal;  
  8.     }  
  9. }  
  10. private T returnVal;  //當前迭代器的Current 值  
  11. private Exception e;    //拋出的錯誤信息  
  12. public Coroutine coroutine;  
  13.   
  14. public IEnumerator InternalRoutine(IEnumerator coroutine){  
  15.     //先省略這部分的處理  
  16.     }  
  17. }  

 InteralRoutine是對返回 Current 值和拋出的異常信息(若是有的話):

C#代碼   收藏代碼
  1. public IEnumerator InternalRoutine(IEnumerator coroutine){  
  2.     while(true){  
  3.         try{  
  4.             if(!coroutine.MoveNext()){  
  5.                 yield break;  
  6.             }  
  7.         }  
  8.         catch(Exception e){  
  9.             this.e = e;  
  10.             yield break;  
  11.         }  
  12.         object yielded = coroutine.Current;  
  13.         if(yielded != null && yielded.GetType() == typeof(T)){  
  14.             returnVal = (T)yielded;  
  15.             yield break;  
  16.         }  
  17.         else{  
  18.             yield return coroutine.Current;  
  19.         }  
  20.     }  

 下面爲這個類擴展MonoBehavior:

C#代碼   收藏代碼
  1. public static class MonoBehaviorExt{  
  2.     public static Coroutine<T> StartCoroutine<T>(this MonoBehaviour obj, IEnumerator coroutine){  
  3.         Coroutine<T> coroutineObject = new Coroutine<T>();  
  4.         coroutineObject.coroutine = obj.StartCoroutine(coroutineObject.InternalRoutine(coroutine));  
  5.         return coroutineObject;  
  6.     }  
  7. }  

 最後給出一個 Example:

C#代碼   收藏代碼
  1. IEnumerator Start () {  
  2.         var routine = StartCoroutine<int>(TestNewRoutine()); //Start our new routine  
  3.         yield return routine.coroutine; // wait as we normally can  
  4.         Debug.Log(routine.Value); // print the result now that it is finished.  
  5.     }  
  6.       
  7.     IEnumerator TestNewRoutine(){  
  8.         yield return null;  
  9.         yield return new WaitForSeconds(2f);  
  10.         yield return 10;  
  11.                 yield return 5;  
  12.     }  

 最後輸出是10,由於Cortoutine<T> 遇到知足條件的 T 類型就 執行 yield break;就不執行 yield return 5; 這條語句了。

若是將中 yield break; 語句去掉的話,最後輸出的是 5 而不是10。

C#代碼   收藏代碼
  1. if(yielded != null && yielded.GetType() == typeof(T)){  
  2.             returnVal = (T)yielded;  
  3.             yield break;  
  4.         }  


其實就是Unity引擎每幀去 check yield return 後面的表達式,若是知足就繼續向下執行。

 

下面在測試一個例子:連續兩次調用 yield return coroutine;

C#代碼   收藏代碼
  1.  private Coroutine routine1;  
  2.  void Start ()   
  3.  {  
  4.     routine1 = StartCoroutine(TestCoroutineExtention1()); //Start our new routine    
  5.     StartCoroutine(TestCortoutine());  
  6.       
  7. }  
  8. IEnumerator TestCoroutineExtention1()  
  9. {  
  10.     yield return new WaitForSeconds(1);  
  11.     yield return 10;  
  12.     Debug.Log("Run 10!");  
  13.     yield return new WaitForSeconds(5);  
  14.     yield return 5;  
  15.     Debug.Log("Run 5!");  
  16. }  
  17. IEnumerator TestCortoutine()  
  18. {  
  19.     //wwwState = true;  
  20.     yield return routine1; // wait as we normally can    
  21.     Debug.Log(" routine1");  
  22.     yield return routine1; // wait as we normally can   
  23.     Debug.Log(" routine2");  
  24. }  

 測試運行會發現只會輸出:

        Run 10!

        Run 5!

         routine1

 

總結下: yield return expression 只有表達式徹底執行結束纔會繼續執行後面的代碼,連續兩次執行 yield return StartCortoutine() 的返回值是不會知足的,說明 yield return 有區分開始和結束的兩種狀態。

 

 

3)Cortoutine Locking

          雖然Cortoutine不是多線程機制,但仍會「併發」問題——同時屢次調用 StartCortoutine ,固然經過Unity提供的api也能獲得解決方案,每次StartCoroutine 以前先調用 StopCortoutine 方法中止,但這利用的是反射,顯然效率很差。④對③的方案進行了擴展提供了 Cortoutine Locking 的支持,使用字符串(方法名)來標記同一個 Coroutine 方法,對於同一個方法若是等待時間超過 timeout 就會終止前面一個 Coroutine 方法,下面直接貼出代碼:

C#代碼   收藏代碼
  1. using UnityEngine;  
  2. using System;  
  3. using System.Collections;  
  4. using System.Collections.Generic;  
  5.   
  6. /// <summary>  
  7. /// Extending MonoBehaviour to add some extra functionality  
  8. /// Exception handling from: http://twistedoakstudios.com/blog/Post83_coroutines-more-than-you-want-to-know  
  9. ///   
  10. /// 2013 Tim Tregubov  
  11. /// </summary>  
  12. public class TTMonoBehaviour : MonoBehaviour  
  13. {  
  14.     private LockQueue LockedCoroutineQueue { getset; }  
  15.               
  16.     /// <summary>  
  17.     /// Coroutine with return value AND exception handling on the return value.   
  18.     /// </summary>  
  19.     public Coroutine<T> StartCoroutine<T>(IEnumerator coroutine)  
  20.     {  
  21.         Coroutine<T> coroutineObj = new Coroutine<T>();  
  22.         coroutineObj.coroutine = base.StartCoroutine(coroutineObj.InternalRoutine(coroutine));  
  23.         return coroutineObj;  
  24.     }  
  25.       
  26.     /// <summary>  
  27.     /// Lockable coroutine. Can either wait for a previous coroutine to finish or a timeout or just bail if previous one isn't done.  
  28.     /// Caution: the default timeout is 10 seconds. Coroutines that timeout just drop so if its essential increase this timeout.  
  29.     /// Set waitTime to 0 for no wait  
  30.     /// </summary>  
  31.     public Coroutine<T> StartCoroutine<T>(IEnumerator coroutine, string lockID, float waitTime = 10f)  
  32.     {  
  33.         if (LockedCoroutineQueue == null) LockedCoroutineQueue = new LockQueue();  
  34.         Coroutine<T> coroutineObj = new Coroutine<T>(lockID, waitTime, LockedCoroutineQueue);  
  35.         coroutineObj.coroutine = base.StartCoroutine(coroutineObj.InternalRoutine(coroutine));  
  36.         return coroutineObj;  
  37.     }  
  38.       
  39.     /// <summary>  
  40.     /// Coroutine with return value AND exception handling AND lockable  
  41.     /// </summary>  
  42.     public class Coroutine<T>  
  43.     {  
  44.         private T returnVal;  
  45.         private Exception e;  
  46.         private string lockID;  
  47.         private float waitTime;  
  48.           
  49.         private LockQueue lockedCoroutines; //reference to objects lockdict  
  50.         private bool lockable;  
  51.           
  52.         public Coroutine coroutine;  
  53.         public T Value  
  54.         {  
  55.             get   
  56.             {   
  57.                 if (e != null)  
  58.                 {  
  59.                     throw e;  
  60.                 }  
  61.                 return returnVal;  
  62.             }  
  63.         }  
  64.           
  65.         public Coroutine() { lockable = false; }  
  66.         public Coroutine(string lockID, float waitTime, LockQueue lockedCoroutines)  
  67.         {  
  68.             this.lockable = true;  
  69.             this.lockID = lockID;  
  70.             this.lockedCoroutines = lockedCoroutines;  
  71.             this.waitTime = waitTime;  
  72.         }  
  73.           
  74.         public IEnumerator InternalRoutine(IEnumerator coroutine)  
  75.         {  
  76.             if (lockable && lockedCoroutines != null)  
  77.             {         
  78.                 if (lockedCoroutines.Contains(lockID))  
  79.                 {  
  80.                     if (waitTime == 0f)  
  81.                     {  
  82.                         //Debug.Log(this.GetType().Name + ": coroutine already running and wait not requested so exiting: " + lockID);  
  83.                         yield break;  
  84.                     }  
  85.                     else  
  86.                     {  
  87.                         //Debug.Log(this.GetType().Name + ": previous coroutine already running waiting max " + waitTime + " for my turn: " + lockID);  
  88.                         float starttime = Time.time;  
  89.                         float counter = 0f;  
  90.                         lockedCoroutines.Add(lockID, coroutine);  
  91.                         while (!lockedCoroutines.First(lockID, coroutine) && (Time.time - starttime) < waitTime)  
  92.                         {  
  93.                             yield return null;  
  94.                             counter += Time.deltaTime;  
  95.                         }  
  96.                         if (counter >= waitTime)  
  97.                         {   
  98.                             string error = this.GetType().Name + ": coroutine " + lockID + " bailing! due to timeout: " + counter;  
  99.                             Debug.LogError(error);  
  100.                             this.e = new Exception(error);  
  101.                             lockedCoroutines.Remove(lockID, coroutine);  
  102.                             yield break;  
  103.                         }  
  104.                     }  
  105.                 }  
  106.                 else  
  107.                 {  
  108.                     lockedCoroutines.Add(lockID, coroutine);  
  109.                 }  
  110.             }  
  111.               
  112.             while (true)  
  113.             {  
  114.                 try   
  115.                 {  
  116.                     if (!coroutine.MoveNext())  
  117.                     {  
  118.                         if (lockable) lockedCoroutines.Remove(lockID, coroutine);  
  119.                         yield break;  
  120.                     }  
  121.                 }  
  122.                 catch (Exception e)  
  123.                 {  
  124.                     this.e = e;  
  125.                     Debug.LogError(this.GetType().Name + ": caught Coroutine exception! " + e.Message + "\n" + e.StackTrace);   
  126.                     if (lockable) lockedCoroutines.Remove(lockID, coroutine);  
  127.                     yield break;  
  128.                 }  
  129.                   
  130.                 object yielded = coroutine.Current;  
  131.                 if (yielded != null && yielded.GetType() == typeof(T))  
  132.                 {  
  133.                     returnVal = (T)yielded;  
  134.                     if (lockable) lockedCoroutines.Remove(lockID, coroutine);  
  135.                     yield break;  
  136.                 }  
  137.                 else  
  138.                 {  
  139.                     yield return coroutine.Current;  
  140.                 }  
  141.             }  
  142.         }  
  143.     }  
  144.       
  145.       
  146.     /// <summary>  
  147.     /// coroutine lock and queue  
  148.     /// </summary>  
  149.     public class LockQueue  
  150.     {  
  151.         private Dictionary<string, List<IEnumerator>> LockedCoroutines { getset; }  
  152.           
  153.         public LockQueue()  
  154.         {  
  155.             LockedCoroutines = new Dictionary<string, List<IEnumerator>>();  
  156.         }  
  157.           
  158.         /// <summary>  
  159.         /// check if LockID is locked  
  160.         /// </summary>  
  161.         public bool Contains(string lockID)  
  162.         {  
  163.             return LockedCoroutines.ContainsKey(lockID);  
  164.         }  
  165.           
  166.         /// <summary>  
  167.         /// check if given coroutine is first in the queue  
  168.         /// </summary>  
  169.         public bool First(string lockID, IEnumerator coroutine)  
  170.         {  
  171.             bool ret = false;  
  172.             if (Contains(lockID))  
  173.             {  
  174.                 if (LockedCoroutines[lockID].Count > 0)  
  175.                 {  
  176.                     ret = LockedCoroutines[lockID][0] == coroutine;  
  177.                 }  
  178.             }  
  179.             return ret;  
  180.         }  
  181.           
  182.         /// <summary>  
  183.         /// Add the specified lockID and coroutine to the coroutine lockqueue  
  184.         /// </summary>  
  185.         public void Add(string lockID, IEnumerator coroutine)  
  186.         {  
  187.             if (!LockedCoroutines.ContainsKey(lockID))  
  188.             {  
  189.                 LockedCoroutines.Add(lockID, new List<IEnumerator>());  
  190.             }  
  191.               
  192.             if (!LockedCoroutines[lockID].Contains(coroutine))  
  193.             {  
  194.                 LockedCoroutines[lockID].Add(coroutine);  
  195.             }  
  196.         }  
  197.           
  198.         /// <summary>  
  199.         /// Remove the specified coroutine and queue if empty  
  200.         /// </summary>  
  201.         public bool Remove(string lockID, IEnumerator coroutine)  
  202.         {  
  203.             bool ret = false;  
  204.             if (LockedCoroutines.ContainsKey(lockID))  
  205.             {  
  206.                 if (LockedCoroutines[lockID].Contains(coroutine))  
  207.                 {  
  208.                     ret = LockedCoroutines[lockID].Remove(coroutine);  
  209.                 }  
  210.                   
  211.                 if (LockedCoroutines[lockID].Count == 0)  
  212.                 {  
  213.                     ret = LockedCoroutines.Remove(lockID);  
  214.                 }  
  215.             }  
  216.             return ret;  
  217.         }  
  218.           
  219.     }  
  220.   
  221. }  

 

小結:

       本文主要是對 Unity StartCoroutine 進行了理解,從C# 的yileld 和 IEnumerator 到 Unity 的 StartCoroutine,最後並對Cortoutine 進行了擴展,雖然感受不是很實用(用到的狀況很是至少),但仍是有利於對Coroutine 的理解和思考。

       對於第三部分的代碼感受有不妥,沒有進行測試,附件裏有代碼,有需求的話請自取

 

        若是您對D.S.Qiu有任何建議或意見能夠在文章後面評論,或者發郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,但願能有更多更好的分享。

       轉載請在文首註明出處:http://dsqiu.iteye.com/blog/2049743

 

更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風) 

 

 

 

參考:

琪琪爸的程序學習筆記 :-Phttp://www.cnblogs.com/easyfrog/archive/2011/12/29/IEnumerable_IEnumerator_yield.html

傑仔http://www.cnblogs.com/illele/archive/2008/04/21/1164696.html

③Twisted Oak Studios: http://twistedoakstudios.com/blog/Post83_coroutines-more-than-you-want-to-know

tim tregubovhttp://zingweb.com/blog/2013/02/05/unity-coroutine-wrapper/

⑤C# in Depth: http://csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx

zhw1125: http://blog.sina.com.cn/s/blog_3e29b20b0100g6ix.html

相關文章
相關標籤/搜索