zoukankan      html  css  js  c++  java
  • Unity C#笔记 协程


    什么是协程

    在Unity中,协程(Coroutines)的形式是我最喜欢的功能之一,我都会使用它来控制需要定时的。

    协同程序,在主程序运行的同时,开启另外一段逻辑处理,来协同当前程序的执行。
    可能看了这段文字介绍还是有点模糊,其实可以用多线程来比较。

    多线程

    多线程,顾名思义,多条同时执行的线程。
    最初,多线程的诞生是为了解决IO阻塞问题,如今多线程可以解决许多同样需要异步方法的问题(例如网络等)。
    所谓异步,通俗点讲,就是我走我的线程,你走你的线程。当某个线程阻塞时,另一个线程不会受影响继续执行。

    需要认识到的是,多线程并不是真正意义上的多条线程同时执行。
    它的实际是将一个时间段分成若干个时间片,每个线程轮流运行一个时间片。

    (如图,将执行步骤切分成极小的粒度,然后依次运行)

    但是由于时间片粒度非常非常小,几乎看不出区别,所以程序执行效果跟真正意义上的并行执行效果基本一致。

    多线程的缺陷

    然而多线程有一个坏处,就是可能造成共享数据的冲突。

    假如有一个变量i = 0, Step1_1的操作是进行++i操作,Step2_1的操作是进行--i操作。
    我们预期最终结果i为0。

    但由于操作切分得过小,可能会发生这样顺序的事:

    • 线程1:访问i, 将0存到寄存器
    • 线程2:访问i, 将0存到寄存器
    • 线程1:++i, 得到1
    • 线程2:--i, 得到-1
    • 线程1:将1写入到i的内存
    • 线程2:将-1写入到i的内存
    • 最终i的值为-1

    当然多线程的冲突也有解决方案: 互斥锁....

    但是这些多多少少会付出额外的代价,让程序变得臃肿。

    协程

    CPU有多条线程,一条线程可以有多个协程。

    协程跟多线程类似,也有类似异步的效果(注意不是真正的异步)。
    只不过它的切分粒度不是基于系统划分的时间片,而是基于我们编写的yield,而且往往粒度更大。

    粒度是取决于自己定义什么时候让协程挂起:

    //下面定义了一个协程函数,注意必须使用IEnumerator作为返还值才能成为协程函数。
    IEnumerator Test()
    {
      for(int i = 0; i<1000 ; ++i){
        ans += i;
        yield return 0;//挂起,下一帧再来从这个位置继续执行。
      }
      j+=2;
      yield return 0;//挂起,下一帧再来从这个位置继续执行。
      ++j;
      yield return 0;//挂起,下一帧再来从这个位置继续执行。
    }
    

    如果划分的粒度过大,协程所在的线程可能在相应的帧卡顿。
    甚至如果让协程阻塞(死循环),那么协程所在的整个线程也会阻塞。
    因此说协程可以有类似异步的效果,但是不是真正的异步。

    协程的一大好处就是可以避免数据访问冲突的问题:
    因为它的粒度相对多线程的大很多,所以往往很少出现冲突现象

    在上面多线程的例子里,使用协程则可以这样:

    • Step1_1: 执行完++i, 此时i=1
    • Step2_1: 执行完--i, 此时i=0
    • 最终i的值为0

    协程的使用场景

    对于保证不会阻塞的并行操作且并行性要求不高的并行操作,可以使用协程。
    更实际来说,协程最常用于延时执行等控制时间轴的操作,例如N秒后调用指定函数。

    利用每帧执行一段协程的特性,我们可以引入个带累加计时判断循环,然后再超过3秒后跳出循环,执行Debug.Log()

    //3s后执行Debug.Log
    IEnumerator Test()
    {
      for(float timer = 0.0f; timer < 3.0f ; timer += Time.DeltaTime){
        yield return 0;//挂起,下一帧再来从这个位置继续执行。
      }
      Debug.Log("启动协程3s后");
    }
    

    但是Unity封装了个更好用的类:WaitForSeconds
    使这种延时的协程代码更加简洁。

      //原本写法
      for(float timer = 0.0f; timer < 3.0f ; timer += Time.DeltaTime){
        yield return 0;//挂起,下一帧再来从这个位置继续执行。
      }
      //使用WaitForSeconds的写法
      yield return new WaitForSeconds(3.0f);
    

    协程使用示例

    接下来就展示下,协程使用的示例:
    首先编写好协程函数

    IEnumerator TestWaitForSeconds()
    {
        //3s后执行Debug.Log;
        yield return new WaitForSeconds(3.0f);
        Debug.Log("启动协程3s后");
    }
    

    然后在某个地方使用StartCoroutine(TestWaitForSeconds())或者StartCoroutine("TestWaitForSeconds")

      //启动协程:3s后执行Debug.log
      StartCoroutine(TestWaitForSeconds());
      //启动后,继续往下执行
      ...
    

    Invoke的缺陷

    另外一提,Unity还有个一样也是用于延时调用的函数,叫Invoke

    Invoke("test",2.0f); \延时2秒后执行函数test
    

    但是Invock所要调用的函数必须是空类型返还值,还必须得是在当前类里面的方法。

    一般来说,用协程来解决这样的问题已经绰绰有余,而且还有更安全的调用方法而不是只用string类型作为参数的方法,因此没必要使用Invoke。

    协程语法

    开启协程

    StartCoroutine(string methodName);
    
    • 参数是方法名(字符串类型),此方法可以包含一个参数。
    • 形参方法可以有返回值
    StartCoroutine(IEnumerator method);
    
    • 参数是方法(TestMethod()),此方法中可以包含多个参数。
    • IEnumrator类型的方法不能含有ref或者out类型的参数,但可以含有被传递的引用
    • 形参方法必须有返回值,且返回值类型为IEnumrator,返回值使用(yield retuen +表达式或者值,或者 yield break)语句

    终止协程

    StopCoroutine(string methodName);//终止指定的协程
    
    • 在程序中调用StopCoroutine()方法只能终止以字符串形式启动的协程
    StopAllCoroutine();//终止所有协程
    

    挂起

    //程序在下一帧中从当前位置继续执行
    yield return 0;
    
    //程序在下一帧中从当前位置继续执行
    yield return null;
    
    //程序等待N秒后从当前位置继续执行
    yield return new WaitForSeconds(N);
    
    //在所有的渲染以及GUI程序执行完成后从当前位置继续执行
    yield new WaitForEndOfFrame();
    
    //所有脚本中的FixedUpdate()函数都被执行后从当前位置继续执行
    yield new WaitForFixedUpdate();
    
    //等待一个网络请求完成后从当前位置继续执行
    yield return WWW;
    
    //等待一个xxx的协程执行完成后从当前位置继续执行
    yield return StartCoroutine(xxx);
    
    //如果使用yield break语句,将会导致协程的执行条件不被满足,不会从当前的位置继续执行程序,而是直接从当前位置跳出函数体,回到函数的根部
    yield break;
    

    协程的执行原理

    TODO

  • 相关阅读:
    Python 中lambda 简单介绍
    关于python中带下划线的变量和函数 的意义
    Python 类的方法,下划线有什么不同?
    Python中用format函数格式化字符串的用法(2.7版本讲解哦!)
    如何理解 Python 中的__init__
    Python引用多个模块,调用模块中的函数时,要注意的地方
    Python tips: 什么是*args和**kwargs?
    如何简单地理解Python中的if __name__ == '__main__'
    NLP点滴——文本相似度
    马里奥AI实现方式探索 ——神经网络+增强学习
  • 原文地址:https://www.cnblogs.com/KillerAery/p/10336388.html
Copyright © 2011-2022 走看看