Unity协程(Coroutine)原理深入剖析再续

By D.S.Qiu

尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com

前面已经介绍过对协程(Coroutine)的认识和理解,主要讲到了Unity引擎在执行协程(Coroutine)的原理(Unity协程(Coroutine)原理深入剖析)和对协程(Coroutine)状态的控制(Unity协程(Coroutine)管理类——TaskManager工具分享),到这使用Coroutine的疑问就没有了,但是D.S.Qiu还是有点没嚼烂,所以觉得很有必要再续。

本文主要分为三部分:

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.

1)yield return, IEnumerator  和 Unity StartCoroutine 的关系和理解

yield 和 IEnumerator都是C#的东西,前者是一个关键字,后者是枚举类的接口。对于IEnumerator 只引用②对 IEnumerable与IEnumerator区别 的论述:

先贴出 IEnumerable 和 IEnumerator的定义:

  1. public interface IEnumerable
  2. {
  3. IEnumerator GetEnumerator();
  4. }
  5. public interface IEnumerator
  6. {
  7. bool MoveNext();
  8. void Reset();
  9. Object Current { get; }
  10. }

IEnumerable和IEnumerator有什么区别?这是一个很让人困惑的问题(在很多forum里都看到有人在问这个问题)。研究了半天,得到以下几点认识:

1、一个Collection要支持foreach方式的遍历,必须实现IEnumerable接口(亦即,必须以某种方式返回IEnumerator object)。

2、IEnumerator object具体实现了iterator(通过MoveNext(),Reset(),Current)。

3、从这两个接口的用词选择上,也可以看出其不同:IEnumerable是一个声明式的接口,声明实现该接口的class是“可枚举(enumerable)”的,但并没有说明如何实现枚举器(iterator);IEnumerator是一个实现式的接口,IEnumerator object就是一个iterator。

4、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的函数得到的:

  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 的处理,这里仅附上一个例子:

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

C#编译器对应生成:

  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. // Nested type automatically created by the compiler to implement the iterator
  9. [CompilerGenerated]
  10. private sealed class <GetCounter>d__0 : IEnumerator<object>, IEnumerator, IDisposable
  11. {
  12. // Fields: there'll always be a "state" and "current", but the "count"
  13. // comes from the local variable in our iterator block.
  14. private int <>1__state;
  15. private object <>2__current;
  16. public int <count>5__1;
  17. [DebuggerHidden]
  18. public <GetCounter>d__0(int <>1__state)
  19. {
  20. this.<>1__state = <>1__state;
  21. }
  22. // Almost all of the real work happens here
  23. private bool MoveNext()
  24. {
  25. switch (this.<>1__state)
  26. {
  27. case 0:
  28. this.<>1__state = -1;
  29. this.<count>5__1 = 0;
  30. while (this.<count>5__1 < 10)        //这里针对循环处理
  31. {
  32. this.<>2__current = this.<count>5__1;
  33. this.<>1__state = 1;
  34. return true;
  35. Label_004B:
  36. this.<>1__state = -1;
  37. this.<count>5__1++;
  38. }
  39. break;
  40. case 1:
  41. goto Label_004B;
  42. }
  43. return false;
  44. }
  45. [DebuggerHidden]
  46. void IEnumerator.Reset()
  47. {
  48. throw new NotSupportedException();
  49. }
  50. void IDisposable.Dispose()
  51. {
  52. }
  53. object IEnumerator<object>.Current
  54. {
  55. [DebuggerHidden]
  56. get
  57. {
  58. return this.<>2__current;
  59. }
  60. }
  61. object IEnumerator.Current
  62. {
  63. [DebuggerHidden]
  64. get
  65. {
  66. return this.<>2__current;
  67. }
  68. }
  69. }
  70. }

从上面的C#实现可以知道:函数内有多少个 yield return 在对应的 MoveNext() 就会返回多少次 true (不包含嵌套)。另外非常重要的一点的是:同一个函数内的其他代码(不是 yield return 语句)会被移到 MoveNext 中去,也就是说,每次 MoveNext 都会顺带执行同一个函数中 yield return 之前,之后 和两个 yield return 之间的代码。

       对于Unity 引擎的 YieldInstruction 实现,其实就可以看着一个 函数体,这个函数体每帧会实现去 check MoveNext 是否返回 false 。 例如:

  1. yield retrun new WaitForSeconds(2f);

上面这行代码的伪代码实现:

  1. private float elapsedTime;
  2. private float time;
  3. private void MoveNext()
  4. {
  5. elapesedTime += Time.deltaTime;
  6. if(time <= elapsedTime)
  7. return false;
  8. else return true;
  9. }

增补于: 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 值和进行错误捕获处理。

先定义封装包裹返回值和错误信息的类:

  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. public IEnumerator InternalRoutine(IEnumerator coroutine){
  14. //先省略这部分的处理
  15. }
  16. }

InteralRoutine是对返回 Current 值和抛出的异常信息(如果有的话):

  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:

  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:

  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. IEnumerator TestNewRoutine(){
  7. yield return null;
  8. yield return new WaitForSeconds(2f);
  9. yield return 10;
  10. yield return 5;
  11. }

 最后输出是10,因为Cortoutine<T> 遇到满足条件的 T 类型就 执行 yield break;就不执行 yield return 5; 这条语句了。

如果将中 yield break; 语句去掉的话,最后输出的是 5 而不是10。

  1. if(yielded != null && yielded.GetType() == typeof(T)){
  2. returnVal = (T)yielded;
  3. yield break;
  4. }


其实就是Unity引擎每帧去 check yield return 后面的表达式,如果满足就继续向下执行。

下面在测试一个例子:连续两次调用 yield return coroutine;

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

测试运行会发现只会输出:

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 方法,下面直接贴出代码:

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

小结:

本文主要是对 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

Unity协程(Coroutine)原理深入剖析再续的更多相关文章

  1. Unity 协程(Coroutine)原理与用法详解

    前言: 协程在Unity中是一个很重要的概念,我们知道,在使用Unity进行游戏开发时,一般(注意是一般)不考虑多线程,那么如何处理一些在主任务之外的需求呢,Unity给我们提供了协程这种方式 为啥在 ...

  2. Unity协程(Coroutine)管理类——TaskManager工具分享

    博客分类: Unity3D插件学习,工具分享 源码分析   Unity协程(Coroutine)管理类——TaskManager工具分享 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处 ...

  3. Unity协程Coroutine使用总结和一些坑

    原文摘自 Unity协程Coroutine使用总结和一些坑 MonoBehavior关于协程提供了下面几个接口: 可以使用函数或者函数名字符串来启动一个协程,同时可以用函数,函数名字符串,和Corou ...

  4. unity协程coroutine浅析

    转载请标明出处:http://www.cnblogs.com/zblade/ 一.序言 在unity的游戏开发中,对于异步操作,有一个避免不了的操作: 协程,以前一直理解的懵懵懂懂,最近认真充电了一下 ...

  5. Unity 协程Coroutine综合测试

    using UnityEngine; using System.Collections; using System.Text; public class rotCube : MonoBehaviour ...

  6. Unity协程(Coroutine)原理深入剖析

    Unity协程(Coroutine)原理深入剖析 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 其实协程并没有那么复杂,网上很多地方都说是多 ...

  7. Unity协程(Coroutine)原理深入剖析(转载)

    记得去年6月份刚开始实习的时候,当时要我写网络层的结构,用到了协程,当时有点懵,完全不知道Unity协程的执行机制是怎么样的,只是知道函数的返回值是IEnumerator类型,函数中使用yield r ...

  8. 【转】Unity协程(Coroutine)原理深入剖析

    Unity协程(Coroutine)原理深入剖析 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 记得去年6月份刚开始实习的时候,当时要我写网 ...

  9. Unity怎样在Editor下运行协程(coroutine)

    在处理Unity5新的AssetBundle的时候,我有一个需求,须要在Editor下(比方一个menuitem的处理函数中,游戏没有执行.也没有MonoBehaviour)载入AssetBundle ...

随机推荐

  1. BZOJ4556 HEOI2016 字符串

    后缀数组. 复习了后缀数组后发现这题真的很好写. 我们只需要将c依次向前向后扩展,找落在[a,b]区间内的最大值,遍历过程中不断用height数组更新. 复杂度就是后缀数组,比主席树的快多了. By: ...

  2. sybase数据库技术 :游标可更新与for read only/for update

    在定义游标时不指定for update 或 for read only,ASE会检查以了解游标是否可更新: 如果游标查询语句中包含order by子句,则ASE会将游标定义为只读:其它情况下定义为可更 ...

  3. MySQL之thread cache

    最近突然对MySQL的连接非常感兴趣,从status根据thread关键字可以查出如下是个状态 show global status like 'thread%'; +---------------- ...

  4. pt-archive提速的实践经验

    最近遇到很多业务需求,需要进行数据导出工作,由于有格式要求,故之前一直使用mysqldump的方法. mysqldump -uuser -ppassword -S mysql.sock -t db t ...

  5. Circuit provides reference for multiple ADCs

    The achievable accuracy for systems with multiple ADCs depends directly on the reference voltages ap ...

  6. Wide-range regulator delivers 12V, 3A output from 16 to 100V source

    Synchronous buck regulators offer high efficiency and are popular in applications in which available ...

  7. wpf 分别用前台和后台 两种方法 绘制矩形 填充

    xaml: <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft ...

  8. Java之基于Eclipse搭建SSH框架(上)

    http://blog.csdn.net/snowwitch/article/details/50925382 http://www.cnblogs.com/hww123/archive/2016/0 ...

  9. 微信公众平台—— 获取微信服务器IP地址

    微信公众平台—— 获取微信服务器IP地址 const ServerIpUrl = 'https://api.weixin.qq.com/cgi-bin/getcallbackip?&acces ...

  10. Tomcat:基础安装和使用教程

    背景 此文记录了 Tomcat 的基本使用方法,主要为了强化记忆. 安装步骤 第一步:下载和安装 Java 下载地址:http://www.oracle.com/technetwork/java/ja ...