当我们在说协程时,我们在说些什么?
能告诉我什么是协程吗?
协程的官方定义是一种具有暂停执行并将控制权返回给Unity,待下一帧时继续执行。通俗点讲就是,协程是一种可以分部执行的函数,即该函数不是每次调用时都会执行函数体内的全部方法,而是只调用其中部分代码。写到这里不知道您有没有发现,该定义有点像IEnumerator的延迟执行。举一个例子:
void Start () { IEnumerator numbers = YieldThreeNumbers (); for (int i = 0; i < 3; i++) { if(!numbers.MoveNext()) break; Debug.Log((int)numbers.Current); } } IEnumerator YieldThreeNumbers() { yield return 1; yield return 2; yield return 3; }
结果:
可以看到当我们执行一次MoveNext方法,才会取得当前当前的值,所以需要循环调用MoveNext才能将全部值取出。
协程也是同样的方法:每一帧都会调用MoveNext方法,期间保存了下一次执行的位置,等到下一帧时会在该位置继续执行。
PS: 在C#中,yield和IEnumerator一起使用时实际上是实现了Itertor模式,详情可参考这篇文章。http://csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx
由此也可以看到,协程其实与多线程一点关系都没有。协程是在主线程中执行的,且每次只能执行一个协程。
协程该怎么用呢?
启动协程
首先我们需要定义一个返回IEnumerator的方法,如:
IEnumerator GetEnumerator() { Yield return null; }
然后在调用StarCoroutine方法,其签名如下:
Coroutine StartCoroutine(string methodName,object value=null);
Coroutine StartCoroutine(IEnumerator routine);
在是使用第一个方法时,我们直接将传入上面定义的方法名:
StartCoroutine(“GetEnumerator”);
注意该方法的参数是IEnumerator,所以我们可以直接将调用上面定义的方法的返回值传入:
StartCoroutine(GetEnumertor());
下面看一个来自官网的例子:
IEnumerator Fade() { for (float f = 1f; f >= 0; f -= 0.1f) { Color c = renderer.material.color; //减少a值,即透明度 c.a = f; renderer.material.color = c; yield return null; } } void Update() { if (Input.GetKeyDown("f")) { //没按一次"f"键,gameObject的透明度都在减少,实现渐隐的效果 StartCoroutine("Fade"); }
当然,我们不仅仅可以yield null,还可以yield其它的表达式:
1. 其它的数值,如0:
和null类似,不过不推荐。因为会存在装箱,拆箱的问题,或多或少会影响性能。
2. WaitForEndOfFrame
等待至所有的Camera和GUI都呈现好了之后执行。
3. WaitForFixedUpdate
等待至所有物理都计算后执行
4. WaitForSeconds
在指定时间段内不执行
5. WWW
等待一个web请求完成
6. 另一个协程
这是比较有意思的一点。因为StartCoroutine的返回值是Coroutine,所以我们可以yield另一个协程。
停止协程
void StopCoroutine(string methodName);
void StopCoroutine(IEnumerator routine);
其中StopCortouine(string methodName)只能停止由与之相对应的StarCoroutine(string methodName)启动的协程。
还有其它的方法来停止协程,但都不是停止某个指定的协程,而是停止多个协程。
void StopAllCoroutines()
停止该behavior内的全部协程
void SetActive(bool value);
将behavior的active设为false后,其内部的协程也都会停止。
等等,我觉得还少了点什么...
协程可以将一个方法,放到多个帧内执行,在很大程度上提高了性能。但协程也是有缺陷的:
- 不支持返回值;
- 不支持异常处理;
- 不支持泛型;
- 不支持锁;
- …
下面我们来解决前三个问题,为协程添加返回值、异常处理和泛型。关于第四个问题的解决方式,请参考最下方的链接:
返回值:
public class ReturnValueCoroutine { private object result; public object Result { get {return result;} } public UnityEngine.Coroutine Coroutine; public IEnumerator InternalRoutine(IEnumerator coroutine) { while(true) { if(!coroutine.MoveNext()){ yield break; } object yielded = coroutine.Current; if(yielded != null){ result = yielded; yield break; } else{ yield return coroutine.Current; } } } } public class Demo : MonoBehaviour { IEnumerator Start () { ReturnValueCoroutine myCoroutine = new ReturnValueCoroutine (); myCoroutine.Coroutine = StartCoroutine (myCoroutine.InternalRoutine(TestNewRoutine())); yield return myCoroutine.Coroutine; Debug.Log (myCoroutine.Result); } IEnumerator TestNewRoutine() { yield return 10; } }
泛型:
public static class MonoBehaviorExt { public static Coroutine<T> StartCoroutine<T>(this MonoBehaviour obj, IEnumerator coroutine){ Coroutine<T> coroutineObject = new Coroutine<T>(); coroutineObject.coroutine = obj.StartCoroutine(coroutineObject.InternalRoutine(coroutine)); return coroutineObject; } } public class Coroutine<T>{ private T result; public T Result { get {return result;} } public Coroutine coroutine; public IEnumerator InternalRoutine(IEnumerator coroutine){ while(true){ if(!coroutine.MoveNext()){ yield break; } object yielded = coroutine.Current; if(yielded != null && yielded.GetType() == typeof(T)){ result = (T)yielded; yield break; } else{ yield return coroutine.Current; } } } } public class Demo : MonoBehaviour { Coroutine<int> routine; IEnumerator Start () { routine = this.StartCoroutine<int>(TestNewRoutine()); //Start our new routine yield return routine; // wait as we normally can } IEnumerator TestNewRoutine(){ yield return null; yield return new WaitForSeconds(2f); yield return 10; } void Update() { //因为延时,所以要等待一段时间才能显示 Debug.Log(routine.Result); } }
异常处理:
public static class MonoBehaviorExt { public static Coroutine<T> StartCoroutine<T>(this MonoBehaviour obj, IEnumerator coroutine){ Coroutine<T> coroutineObject = new Coroutine<T>(); coroutineObject.coroutine = obj.StartCoroutine(coroutineObject.InternalRoutine(coroutine)); return coroutineObject; } } public class Coroutine<T>{ public T Result { get{ if(e != null){ throw e; } return result; } } private T result; private Exception e; public UnityEngine.Coroutine coroutine; public IEnumerator InternalRoutine(IEnumerator coroutine){ while(true){ try{ if(!coroutine.MoveNext()){ yield break; } } catch(Exception e){ this.e = e; yield break; } object yielded = coroutine.Current; if(yielded != null && yielded.GetType() == typeof(T)){ result = (T)yielded; yield break; } else{ yield return coroutine.Current; } } } } public class Demo : MonoBehaviour { IEnumerator Start () { var routine = this.StartCoroutine<int>(TestNewRoutineGivesException()); yield return routine.coroutine; try{ Debug.Log(routine.Result); } catch(Exception e){ Debug.Log(e.Message); // do something Debug.Break(); } } IEnumerator TestNewRoutineGivesException(){ yield return null; yield return new WaitForSeconds(2f); throw new Exception("Bad thing!"); } }
你说的我都知道了,还有别的吗?
1. 使用lamada表达式接受返回值:http://answers.unity3d.com/questions/207733/can-coroutines-return-a-value.html
2. Wrapping Unity C# Coroutines for Exception Handling, Value Retrieval, and Locking:http://zingweb.com/blog/2013/02/05/unity-coroutine-wrapper/
3. CoroutineScheduler:http://wiki.unity3d.com/index.php?title=CoroutineScheduler
以上是本人的学习成果及平时收集的资料,如果您有其它更好的资源或是想法,请怒砸至评论区,多多益善!
参考资料:Coroutines – More than you want to know,http://twistedoakstudios.com/blog/Post83_coroutines-more-than-you-want-to-know