U2L7——协同程序

协同程序

协同程序简称协程,它是“假”的多线程,它不是多线程

它的主要作用:
将代码分时执行,不卡主线程
简单理解,是把可以会让主线程卡顿的耗时的逻辑分时分步执行

主要使用场景:
异步加载文件,异步下载文件,场景异步加载,批量创建时防止卡顿

Unity支持多线程,只是新开线程无法访问主线程中Unity相关内容
一般主要用于进行复杂逻辑运算或者网络消息接收等等
注意:Unity中的多线程一定要记得关闭!!!

对于协程的原理,请看协同程序原理

总结:
协同程序(协程)不是多线程,它是将线程中逻辑进行分时执行,避免卡顿,继承MonoBehaviour​的类都可以使用协程
协程只有当组件单独失活时不受影响 其他情况下协程会停止
这个属于MonoBehaviour​基类的重要方法,之前的重要方法看这里 -> MonoBehaviour​

本章代码关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
IEnumerator                                	//本质实现是迭代器所需的接口,在这里只有返回值为该接口的函数,才能成为协程的函数
Coroutine //协程类,开启协程后可以用这种变量类型装载,以管理线程
StartCoroutine() //将返回值为迭代器接口的函数或迭代器接口的变量填入,即可开启线程,可以用Coroutine来装载其返回的协程
StopAllCoroutines() //会停止自己类里所有还在执行的协程(不影响其它类的协程)
StopCoroutine() //用协程变量来指定关闭其中存放的协程
yield return //在迭代器里这个是返回但不结束函数的语法糖,但在协程yield return是协程函数的分界点
yield return null; //挂起协程,直到下一帧再执行协程后续逻辑
yield return 数字; //挂起协程,指定几帧后再执行协程后续逻辑
yield return new WaitForSeconds(); //挂起协程,等待指定秒后执行协程后续逻辑
yield return new WaitForFixedUpdate(); //挂起协程,等待下一个固定物理帧更新时执行协程后续逻辑
yield return new WaitForEndOfFrame(); //挂起协程,等待摄像机和GUI渲染完成后执行协程后续逻辑
yield break; //跳出协程
yield return new WaitForSecondsRealtime(); //挂起协程,等待指定秒后执行,不受Time.timeScale影响的

Unity是否支持多线程

首先明确,Unity支持多线程
但是新开线程无法访问Unity对象相关的内容
多线程的意义在于,可以将复杂逻辑的计算放进多线程的计算
注意:Unity中的多线程 要记住关闭(不关闭会导致线程与编辑器共生)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
t = new Thread(Test);
//t.Start();
//注意:Unity中的多线程 要记住关闭(不关闭会导致线程与编辑器共生)
private void Test()
{
while (true)
{
Thread.Sleep(1000);
//相当于模拟 复杂算法 算出了一个结果 然后放入公共容器中
System.Random r = new System.Random();
queue.Enqueue(new Vector3(r.Next(-10, 10), r.Next(-10, 10), r.Next(-10, 10)));
print("123");
}
}

协同程序和线程的区别

新开一个线程是独立一个管道,和主线程并行执行
新开一个协程是在原线程之上开启,进行逻辑分时分步执行

协程的使用

继承MonoBehaviour​的类 都可以开启 协程函数

声明协程函数

协程函数有两个关键点

  1. 返回值为IEnumerator​类型及其子类
  2. 函数中通过 yield return 返回值;​ 进行返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//关键点一:协同程序(协程)函数 返回值 必须是 IEnumerator或者继承它的类型
IEnumerator MyCoroutine(int i, string str)
{
print(i);
//关键点二:协程函数当中 必须使用yield return 来进行返回
yield return new WaitForSeconds(5f); //相当于等五秒再执行
print(str);
yield return new WaitForSeconds(1f); //yield return是函数的分界点,n个yield return就把函数分为n+1个部分
print("2");
yield return new WaitForSeconds(1f); //遇到yield return就会将函数挂起,根据yield return后面的内容来决定什么时候再开始执行
print("3");

while (true)
{
print(5);
yield return new WaitForSeconds(5f); //可以在协程内写死循环,让协程被分为无数步,这样不会卡死程序
}
}

开启协程函数

协程函数是不能这样直接执行的!!!这样执行没有任何效果:MyCoroutine(1, "123");​​

常用开启方式:

1
2
3
Coroutine c1 = StartCoroutine(MyCoroutine(1, "123"));
Coroutine c2 = StartCoroutine(MyCoroutine(1, "123"));
Coroutine c3 = StartCoroutine(MyCoroutine(1, "123"));

或者这样开启

1
2
IEnumerator ie = MyCoroutine(1, "123");
StartCoroutine(ie);

关闭协程

关闭所有协程

会停止自己类里所有还在执行的协程(不影响其它类的协程)

1
StopAllCoroutines();    //会停止自己类里所有还在执行的协程(不影响其它类的协程)

关闭指定协程

1
StopCoroutine(c1);  //用协程变量来指定关闭其中存放的协程

yield return

yield return​是函数的分界点,n个yield return​就把函数分为n+1个部分
遇到yield return​就会将函数挂起,根据yield return​后面的内容来决定什么时候再开始执行
可以在协程内写死循环,让协程被分为无数步,这样不会卡死程序

下一帧或者等待指定帧后执行

Update​和LateUpdate​之间执行

1
2
yield return 数字;
yield return null;

等待指定秒后执行

Update​和LateUpdate​之间执行,它受 Time.timeScale​ 影响

1
yield return new WaitForSeconds(秒数);

等待下一个固定物理帧更新时执行

FixedUpdate​和碰撞检测相关函数之后执行

1
yield return new WaitForFixedUpdate();

等待摄像机和GUI渲染完成后执行

LateUpdate​之后的渲染相关处理完毕后之后

1
yield return new WaitForEndOfFrame();

一些特殊类型的对象 比如异步加载相关函数返回的对象
之后讲解 异步加载资源 异步加载场景 网络加载时再讲解
一般在Update​和LateUpdate​之间执行

跳出协程

1
yield break;

不受Time.timeScale​影响的等待指定秒后执行

Update​和LateUpdate​之间执行,顾名思义,相对于yield return new WaitForSeconds()​,它不受 Time.timeScale​ 影响

1
yield return new WaitForSecondsRealtime();

协程受对象和组件失活销毁的影响

协程开始后:

  • 组件或者物体销毁,协程无法执行
  • 物体失活协程不执行,组件失活协程执行

关于Unity协程内的泛型方法

C# 的泛型方法,一定要传型参调用,因为这是编译检查的规划。不然编译就通不过。但unity的协程中就有例外呢?
Unity 的协程是特定于 Unity 引擎的功能,与 C# 泛型方法的编译时类型检查是不同的概念。在其他 C# 应用程序中,需要遵循泛型方法的规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System.Collections;
using UnityEngine;

public class L14Try : MonoBehaviour
{
private void Start()
{
StartCoroutine(MyCoroutine());
}

private IEnumerator MyCoroutine()
{
Debug.Log("协程开始");
yield return new WaitForSeconds(2.0f);
CallGenericMethod("你好协程");
Debug.Log("可见,Unity的协程里的泛型方法不需要传入形参");
}

private void CallGenericMethod<T>(T value)
{
Debug.Log("泛型方法传入的参数是" + value.ToString());
}
}