franciscochonge 发表于 2021-12-26 21:06

Unity协同程序

一.协程的概念:   -----https://www.jianshu.com/p/cfd52c783a8d
1.协程是一个分部执行,遇到条件(yield return ……)会挂起,直到条件满足才能被唤醒,继续执行后边的代码。

2.Unity在每一帧都会处理对象上的协程。主要是在Updata后,去检查协程的条件是否被满足。

二.协程的写法
Unity的协程系统是基于C#的一个简单而强大的接口,IEnumerator允许你为自己的集合类型编写枚举器。也就是一个返回值,一个yield return null语句。

//不带参数的:
IEnumerator Parameter()

// 协程中必须有返回值yield return
yield return null;

//带参数的:
IEnumerator NoParameter(string name)

print (name);
yield return null;

三.协程的启动与关闭
1.StartCoroutine();开始协程

2.StopCoroutine(string methodName);停止协程

3.StopAllCoroutines停止运行此Behaviour上的所有协程

设置gameobject的active为false时可以终止协同程序,但是再次设置为true后协程不会再启动。

四.协程中注意事项
1.协程和普通方法一样,可以被多次调用。

2.协程一旦被开启之后,总是试图将方法中的代码执行完,之后停止。

3.在协程内,如果遇到yield return null,0,1……;表示剩余代码将在n秒之后执行。

4.在协程内遇到 yield return new WaitForSeconds(n);表示剩余代码将在n秒后执行。(受时间缩放的影响)

5.在协程中遇到 yield return StartCoroutine(Son()),表示剩余代码将在子协程执行完成之后举行执行(子协程仍然满足协程基本规则)。

6.在协程中如果遇到 yield return new WaitForFixedUpdate(),表示剩余代码将在FixedUpdate执行完毕之后继续向下执行。

7.在协程中如果遇到 yield return new WaitForEndOfFrame(),表示剩余代码将在ONGUI执行完毕之后继续向下执行。

8.在协程中如果遇到 yield return WWW,表示剩余代码将在www下载文件之后继续执行。

9.在协程中如果遇到 yield return obj,表示剩余代码将在obj不为空的时候继续向下执行。

10.协程方法,可以当做普通方法,在两个脚本之间自由调用。声明周期中的方法,也可以改造成协程方法。

11.当某一个脚本中的协程在执行过程中,如果我们将该脚本的enable设置为false,协程不会停止。只有将挂载该脚本的物体设置为SetActive(false)时才会停止,但是再次设置为true后协程不会再启动。

12.不能在Updata或者FixedUpdata方法中使用协程,否则会报错。

协程 与 多线程

在Unity中,协程(Coroutines)的形式是非常常用的功能之一,使用它来控制程序的先后执行。

也就是在主程序运行的同时,如需要开启另外一段逻辑处理,来协同当前程序的执行,这时,就需要用到协同程序了。

单纯的对协同进行描述,会比较懵逼,我们可以借用多线程来比较。

多线程,做Java的就非常熟悉了,就是多条同时执行的线程。
最初,多线程的诞生是为了解决IO阻塞问题,如今多线程可以解决许多同样需要异步方法的问题(例如网络等)。
所谓异步,就是相互独立执行,过程互不影响,即其中一个线程阻塞时,另一个线程不会受影响继续执行。

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

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

image

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


无论java还是c#,多线程有一个坏处,就是可能造成共享数据的冲突。

假如有一个变量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;//挂起,下一帧再来从这个位置继续执行。}如果划分的粒度过大,协程所在的线程可能在相应的帧卡顿。甚至如果让协程阻塞(死循环),那么协程所在的整个线程也会阻塞。因此说协程可以有类似异步的效果,但是不是真正的异步。!(//upload-images.jianshu.io/upload_images/12972541-81746d191a514678.png?imageMogr2/auto-orient/strip|imageView2/2/w/726/format/webp)协程的一大好处就是可以避免数据访问冲突的问题:因为它的粒度相对多线程的大很多,所以往往很少出现冲突现象在上面多线程的例子里,使用协程则可以这样:
Step1_1: 执行完++i, 此时i=1Step2_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(){    Debug.Log("启动协程方法 TestWaitForSeconds");    //等待2s后执行;    yield return new WaitForSeconds(2.0f);    Debug.Log("开始执行方法 TestWaitForSeconds");}
测试方法写好后,就可以在调用的地方使用,调用方式跟普通的方法调用有点不一样,它需要调用系统方法StartCoroutine(), 如何测试方法作为参数:
StartCoroutine(TestWaitForSeconds())// 或者StartCoroutine("TestWaitForSeconds")
另外,也可以用传参的方式
StartCoroutine(TestWaitForSeconds(5f))IEnumerator TestWaitForSeconds(float delay){    Debug.Log("启动协程方法 TestWaitForSeconds");    //等待delay后执行;    yield return new WaitForSeconds(delay);    Debug.Log("开始执行方法 TestWaitForSeconds");}三、协程语法

1、开启协程


上面也简单介绍了开启方式,下面详细讲解一下。
StartCoroutine(string methodName);
参数是方法名(字符串类型),此方法可以包含一个参数。形参方法可以有返回值
StartCoroutine(IEnumerator method);
参数是方法(TestMethod()),此方法中可以包含多个参数。IEnumrator类型的方法不能含有ref或者out类型的参数,但可以含有被传递的引用形参方法必须有返回值,且返回值类型为IEnumrator,返回值使用(yield retuen +表达式或者值,或者 yield break)语句
2、终止协程

终止指定的协程


只能终止以字符串形式启动的协程
StartCoroutine(string methodName);
终止方式:
StopCoroutine(string methodName);终止所有协程

StopAllCoroutine();3、挂起协程

//程序在下一帧中从当前位置继续执行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;3、协程组合使用


了解到挂起携程的方法后,我们就可以组合成一些较为复杂一点的使用方法,如,套上for循环,执行一系列消耗时间操作
IEnumerator dongSomeThing(){for(int i =0; i < 100; i++){      yield return StartCoroutin(dongSomeThing());      i++;}    Debug.Log("dongSomeThing()执行完毕");}IEnumeratordongSomeThing(int index){    yield return new WaitForSeconds(2f);    Debug.Log("dongSomeThing() index " + index + " 执行完毕");}协程的执行原理

IEnumerator


协程函数的返回值是IEnumerator,它是一个迭代器,可以把它当成执行一个序列的某个节点的指针。
它提供了两个重要的接口,分别是
Current:返回当前指向的元素。MoveNext:将指针向后移动一个单位,如果移动成功,则返回true。
yield


yield关键词用来声明序列中的下一个值或者是一个无意义的值。

当我们这样挂起携程,yield return x(x是指一个具体的对象或者数值)
MoveNext返回为trueCurrent被赋值为x

当我们这样挂起携程,yield break
MoveNext()返回为false

也就是说

如果MoveNext函数返回为true意味着协程的执行条件被满足,则能够从当前的位置继续往下执行。否则不能从当前位置继续往下执行。
页: [1]
查看完整版本: Unity协同程序