zoukankan      html  css  js  c++  java
  • 当我们在说协程时,我们在说些什么?

    能告诉我什么是协程吗?

    协程的官方定义是一种具有暂停执行并将控制权返回给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;
    }
    View Code

    结果:

    可以看到当我们执行一次MoveNext方法,才会取得当前当前的值,所以需要循环调用MoveNext才能将全部值取出。

    协程也是同样的方法:每一帧都会调用MoveNext方法,期间保存了下一次执行的位置,等到下一帧时会在该位置继续执行。

    PS: 在C#中,yield和IEnumerator一起使用时实际上是实现了Itertor模式,详情可参考这篇文章。http://csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx

    由此也可以看到,协程其实与多线程一点关系都没有。协程是在主线程中执行的,且每次只能执行一个协程。

    协程该怎么用呢?

    启动协程

    首先我们需要定义一个返回IEnumerator的方法,如:

    IEnumerator GetEnumerator()
    {
        Yield return null;
    }
    View Code

    然后在调用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");
    }
        
    View Code

    当然,我们不仅仅可以yield null,还可以yield其它的表达式:

    1. 其它的数值,如0:

    和null类似,不过不推荐。因为会存在装箱,拆箱的问题,或多或少会影响性能。

    2. WaitForEndOfFrame

    等待至所有的Camera和GUI都呈现好了之后执行。

    3. WaitForFixedUpdate

    等待至所有物理都计算后执行

    4. WaitForSeconds

    在指定时间段内不执行

    5. WWW

    等待一个web请求完成

    6. 另一个协程

    这是比较有意思的一点。因为StartCoroutine的返回值是Coroutine,所以我们可以yield另一个协程。

    要注意的是,如果设置Time.timeScale为0时,yield return new WaitForSeconds(x)是不会恢复继续执行。

    停止协程

    void StopCoroutine(string methodName);

    void StopCoroutine(IEnumerator routine);

    其中StopCortouine(string methodName)只能停止由与之相对应的StarCoroutine(string methodName)启动的协程。

    还有其它的方法来停止协程,但都不是停止某个指定的协程,而是停止多个协程。

    void StopAllCoroutines()

    停止该behavior内的全部协程

    void SetActive(bool value);

    将behavior的active设为false后,其内部的协程也都会停止。

    等等,我觉得还少了点什么...

    协程可以将一个方法,放到多个帧内执行,在很大程度上提高了性能。但协程也是有缺陷的:

    1. 不支持返回值;
    2. 不支持异常处理;
    3. 不支持泛型;
    4. 不支持锁;

    下面我们来解决前三个问题,为协程添加返回值、异常处理和泛型。关于第四个问题的解决方式,请参考最下方的链接:

    返回值:

    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;
        }
    }
    View Code

    泛型:

    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); 
        }
    }
    View Code

    异常处理:

    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!");
        }
    }
    View Code

    你说的我都知道了,还有别的吗?

    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

  • 相关阅读:
    Enum, Generic and Templates
    Writing A Threadpool in Rust
    A First Look at Rust Language
    Closures in OOC
    Complexity Behind Closure
    Introduction to OOC Programming Language
    OOC,泛型,糟糕的设计。
    Enlightenment笔记
    Machine Learning/Random Projection
    Machine Learning/Introducing Logistic Function
  • 原文地址:https://www.cnblogs.com/mezero/p/3953838.html
Copyright © 2011-2022 走看看