15.MonoBehavior中的重要内容-协同程序
15.1 知识点
Unity是否支持多线程?
- Unity是支持多线程的,只是新开线程无法访问Unity相关对象的内容。例如this.transform。
- 注意:Unity中的多线程 要记住关闭 不然的话会和Unity这个编辑器共生 就算停止运行也会继续执行新线程内容 可以在OnDestroy执行关闭线程逻辑
- 在Unity中,不会开启多线程访问Unity相关。当射涉及复杂逻辑的计算的时候,假如都放到主线程里,可能会造成主线程的卡顿。这时就可以开启多线程用于A星寻路算法,网络收发相关。当算好了结果或者收到了消息过后,放在公共的内存区域。主线程判断判断公共的内存区域有没有想要的对象,拿来使用。
//新线程
Thread newThread;
//假设寻路算法会算出来一个点 申明一个变量作为一个公共内存容器 主线程要用就判断是否有内容往里面拿东西
Queue<Vector3> queue = new Queue<Vector3>();
//副线程可能也要拿主线程的数据用来判断做逻辑
Queue<Vector3> queue2 = new Queue<Vector3>();
void Start()
{
//首先要明确一点
//Unity是支持多线程的
//只是新开线程无法访问Unity相关对象的内容
//引用System.Threading命名空间
newThread = new Thread(newThreadLogic);
newThread.Start();
//注意:
//Unity中的多线程 要记住关闭
//不然的话会和Unity这个编辑器共生 就算停止运行也会继续执行新线程内容
//可以在OnDestroy执行关闭线程逻辑
}
void Update()
{
//在主线程判断 副线程有没有往队列里放东西,有的话就拿出来用
if (queue.Count > 0)
{
//取出位置
this.transform.position = queue.Dequeue();
}
}
//新线程逻辑
private void newThreadLogic()
{
while (true)
{
Thread.Sleep(1000);
print("新线程逻辑 每隔一秒的打印");
//我们在Unity中,不会开启多线程访问Unity相关
//当射涉及复杂逻辑的计算的时候,假如都放到主线程里,可能会造成主线程的卡顿
//这时就可以开启多线程用于A星寻路算法,网络收发相关
//当算好了结果或者收到了消息过后,放在公共的内存区域
//主线程判断判断公共的内存区域有没有想要的对象,拿来使用
//这句代码会报错 不能在副线程控制Unity相关
//UnityException: get_transform can only be called from the main thread.
//this.transform.Translate(Vector3.forward * Time.deltaTime);
//相当于模拟 复杂算法 算出了一个结果 然后放入公共容器中
//用Unity的随机数都不能用 用C#里的随机数
System.Random r = new System.Random();
queue.Enqueue(new Vector3(r.Next(-10, 10), r.Next(-10, 10), r.Next(-10, 10)));
}
}
private void OnDestroy()
{
//关闭线程
newThread.Abort();
//关闭线程后置空
newThread = null;
}
协同程序是什么?
- 协同程序简称协程
- 它是“假”的多线程,它不是多线程
- 它的本质是迭代器
协程的主要作用
- 将代码分时执行,不卡主线程
- 简单理解,是把可能会让主线程卡顿的耗时的逻辑分时分步执行
主要使用场景
- 异步加载文件
- 异步下载文件
- 场景异步加载
- 批量创建时防止卡顿
协同程序和线程的区别
新开一个线程是独立的一个管道,与主线程并行执行。
新开一个协程是在原线程之上开启,进行逻辑分时分步执行。
- 可以理解为,在主线程开启了一个别的分支,将协程拆分成多部分按逻辑分时分布执行。
- 在一次主线程循环中,可能只执行了协程的某一部分。
- 根据返回的逻辑,决定下次执行时机。
- 未执行时,协程处于挂起状态,等待下次执行时继续执行后续部分。
- 直到协程的每一部分都执行完毕,这个协程才算执行完毕。
协同程序和线程的区别通俗解释:
- 多线程如同两根管道。
- 协程是在线程上开启的一个分支,进行分时分步的执行。
协程的使用
- 继承MonoBehavior的类 都可以开启 协程函数
声明协程函数
//申明协程函数
//关键点一: 协同程序(协程)函数 返回值 必须是 IEnumerator或者继承它的类型
IEnumerator MyCoroutine(int i, string str)
{
//关键点二: 协程函数当中 必须使用 yield return 进行返回
print("传进来的int值" + i);
print("yield return 任意数字 或 yield return null");
print("下一帧后在Update和LateUpdate之间再执行后面的逻辑");
yield return 123;
yield return null;
print("传进来的string值" + str);
print("yield return new WaitForSeconds(3f)");
print("等待3秒后在Update和LateUpdate之间再执行后面的逻辑");
yield return new WaitForSeconds(3f);
}
StartCoroutine方法 开启协程函数
void Start()
{
//协程函数 是不能够 直接这样去执行的!!!!!!!
//直接这样调用函数执行没有任何效果
//MyTimerCoroutine(1, "123");
//MonoBehaviour中的StartCoroutine方法 开启协程
//启动协程。
//常用开启协程的方式
//注意:StartCoroutine会返回一个Coroutine对象 可以用一个Coroutine变量装起来 可以用于指定协程的关闭
//用迭代器接口变量接一个协程函数 在丢进StartCoroutine执行
IEnumerator ie = MyCoroutine(1, "111");
Coroutine c1 = StartCoroutine(ie);
//直接把协程函数丢进StartCoroutine执行
Coroutine c2 = StartCoroutine(MyCoroutine(2, "222"));
Coroutine c3= StartCoroutine(MyCoroutine(3, "333"));
Coroutine c4 = StartCoroutine(MyCoroutine(4, "444"));
//第三步:关闭协程
//MonoBehaviour中的StopAllCoroutines方法 关闭所有协程
//停止在该行为上运行的所有协同程序。
//StopAllCoroutines();
//MonoBehaviour中的StopCoroutine方法 关闭指定协程
//停止在该行为上运行的第一个名为 methodName 的协同程序或存储在 routine 中的协同程序。
//一般都通过关闭返回的协程对象进行关闭 不推荐传入字符串进行关闭
StopCoroutine(c2);
}
StopCoroutine和StopAllCoroutines方法 关闭协程函数
void Start()
{
//...
//MonoBehaviour中的StopAllCoroutines方法 关闭所有协程
//停止在该行为上运行的所有协同程序。
//StopAllCoroutines();
//MonoBehaviour中的StopCoroutine方法 关闭指定协程
//停止在该行为上运行的第一个名为 methodName 的协同程序或存储在 routine 中的协同程序。
//一般都通过关闭返回的协程对象进行关闭 不推荐传入字符串进行关闭
StopCoroutine(c2);
}
yield return 不同内容的含义
IEnumerator MyCoroutine(int i, string str)
{
//1.下一帧执行
//yield return 数字;
//yield return null;
//在Update和LateUpdate之间执行
//2.等待指定秒后执行
//yield return new WaitForSeconds(秒);
//在Update和LateUpdate之间执行
//3.等待下一个固定物理帧更新时执行
//yield return new WaitForFixedUpdate();
//在FixedUpdate和碰撞检测相关函数之后执行
//4.等待摄像机和GUI渲染完成后执行
//yield return new WaitForEndOfFrame();
//在LateUpdate之后的渲染相关处理完毕后之后
//5.一些特殊类型的对象 比如异步加载相关函数返回的对象
//之后讲解 异步加载资源 异步加载场景 网络加载时再讲解
//一般在Update和LateUpdate之间执行
//6.跳出协程
//yield break;
print("传进来的int值" + i);
print("yield return 任意数字 或 yield return null");
print("下一帧后在Update和LateUpdate之间再执行后面的逻辑");
yield return 123;
yield return null;
print("传进来的string值" + str);
print("yield return new WaitForSeconds(3f)");
print("等待3秒后在Update和LateUpdate之间再执行后面的逻辑");
yield return new WaitForSeconds(3f);
print("WaitForFixedUpdate()");
print("等待下一个固定物理帧更新时再执行后面的逻辑");
yield return new WaitForFixedUpdate();
print("WaitForEndOfFrame()");
print("等待摄像机和GUI渲染完成后再执行后面的逻辑");
//主要会用来 截图时 会使用 截图一般是要渲染完再截图
yield return new WaitForEndOfFrame();
print("跳出协程");
//注意:直接跳出协程后再StopCoroutine(Coroutine协程对象),会报错,跳出后代表这个协程已经结束了
yield break;
//协程函数是可以写死循环的
while (true)
{
print("死循环内 yield return new WaitForSeconds(5f);");
print("死循环内 等待5秒后在Update和LateUpdate之间再执行后面的逻辑");
yield return new WaitForSeconds(5f);
}
}
协程受对象和组件失活销毁的影响
- 协程开启后:
- 组件或物体销毁,物体失活,则协程不再执行。
- 组件失活时,协程仍然执行。
总结
- Unity支持多线程,只是新开线程无法访问主线程中Unity相关内容。一般主要用于进行复杂逻辑运算或者网络消息接收等等。注意:Unity中的多线程一定记住关闭,可以在OnDestroy关闭。
- 协同程序不是多线程,它是将线程中逻辑进行分时执行,避免卡顿。
- 继承MonoBehavior的类都可以使用协程。
- 开启协程方法、关闭协程方法。
- yield return 返回的内容对于我们的意义。
- 协程只有当组件单独失活时不受影响,其它情况协程会停止。
15.2 知识点代码
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class Lesson15_MonoBehavior中的重要内容_协同程序 : MonoBehaviour
{
//新线程
Thread newThread;
//假设寻路算法会算出来一个点 申明一个变量作为一个公共内存容器 主线程要用就判断是否有内容往里面拿东西
Queue<Vector3> queue = new Queue<Vector3>();
//副线程可能也要拿主线程的数据用来判断做逻辑
Queue<Vector3> queue2 = new Queue<Vector3>();
void Start()
{
#region 知识点一 Unity是否支持多线程?
//首先要明确一点
//Unity是支持多线程的
//只是新开线程无法访问Unity相关对象的内容
//引用System.Threading命名空间
newThread = new Thread(newThreadLogic);
newThread.Start();
//注意:
//Unity中的多线程 要记住关闭
//不然的话会和Unity这个编辑器共生 就算停止运行也会继续执行新线程内容
//可以在OnDestroy执行关闭线程逻辑
#endregion
#region 知识点二 协同程序是什么?
//协同程序简称协程
//它是“假”的多线程,它不是多线程
//他的本质是迭代器
//它的主要作用
//将代码分时执行,不卡主线程
//简单理解,是把可能会让主线程卡顿的耗时的逻辑分时分步执行
//主要使用场景
//异步加载文件
//异步下载文件
//场景异步加载
//批量创建时防止卡顿
#endregion
#region 知识点三 协同程序和线程的区别
//新开一个线程是独立的一个管道,和主线程并行执行
//新开一个协程是在原线程之上开启,进行逻辑分时分步执行
//可以理解为 在主线程开启了一个别的分支 】
//把协同程序拿了出来 分成了多部分 按逻辑分时分布的去执行
//有可能在一次主线程循环中 只执行了某一部分
//根据返回的逻辑 决定下次执行是什么时候
//在未执行的时候 协程就处于挂起状态 等待下次执行时继续执行协程后续的部分
//直到协程函数每一部分都执行完 这个协程才算执行完毕
//协同程序和线程的区别通俗解释
//多线程是两根管道
//协程是线程上开启一个分支进行分时分步的执行
#endregion
#region 知识点四 协程的使用
//继承MonoBehavior的类 都可以开启 协程函数
//第一步:申明协程函数
// 协程函数2个关键点
// 1-1返回值为IEnumerator类型及其子类
// 1-2函数中通过 yield return 返回值; 进行返回
//第二步:开启协程函数
//协程函数 是不能够 直接这样去执行的!!!!!!!
//直接这样调用函数执行没有任何效果
//MyTimerCoroutine(1, "123");
//MonoBehaviour中的StartCoroutine方法 开启协程
//启动协程。
//常用开启协程的方式
//注意:StartCoroutine会返回一个Coroutine对象 可以用一个Coroutine变量装起来 可以用于指定协程的关闭
//用迭代器接口变量接一个协程函数 在丢进StartCoroutine执行
IEnumerator ie = MyCoroutine(1, "111");
Coroutine c1 = StartCoroutine(ie);
//直接把协程函数丢进StartCoroutine执行
Coroutine c2 = StartCoroutine(MyCoroutine(2, "222"));
Coroutine c3= StartCoroutine(MyCoroutine(3, "333"));
Coroutine c4 = StartCoroutine(MyCoroutine(4, "444"));
//第三步:关闭协程
//MonoBehaviour中的StopAllCoroutines方法 关闭所有协程
//停止在该行为上运行的所有协同程序。
//StopAllCoroutines();
//MonoBehaviour中的StopCoroutine方法 关闭指定协程
//停止在该行为上运行的第一个名为 methodName 的协同程序或存储在 routine 中的协同程序。
//一般都通过关闭返回的协程对象进行关闭 不推荐传入字符串进行关闭
StopCoroutine(c2);
#endregion
#region 知识点五 yield return 不同内容的含义
//1.下一帧执行
//yield return 数字;
//yield return null;
//在Update和LateUpdate之间执行
//2.等待指定秒后执行
//yield return new WaitForSeconds(秒);
//在Update和LateUpdate之间执行
//3.等待下一个固定物理帧更新时执行
//yield return new WaitForFixedUpdate();
//在FixedUpdate和碰撞检测相关函数之后执行
//4.等待摄像机和GUI渲染完成后执行
//yield return new WaitForEndOfFrame();
//在LateUpdate之后的渲染相关处理完毕后之后
//5.一些特殊类型的对象 比如异步加载相关函数返回的对象
//之后讲解 异步加载资源 异步加载场景 网络加载时再讲解
//一般在Update和LateUpdate之间执行
//6.跳出协程
//yield break;
#endregion
#region 知识点六 协程受对象和组件失活销毁的影响
//协程开启后
//组件或物体销毁,物体失活 协程不执行
//组件失活 协程执行
#endregion
#region 总结
//1.Unity支持多线程,只是新开线程无法访问主线程中Unity相关内容
// 一般主要用于进行复杂逻辑运算或者网络消息接收等等
// 注意:Unity中的多线程一定记住关闭 可以在OnDestroy关闭
//2.协同程序不是多线程,它是将线程中逻辑进行分时执行,避免卡顿
//3.继承MonoBehavior的类都可以使用协程
//4.开启协程方法、关闭协程方法
//5.yield return 返回的内容对于我们的意义
//6.协程只有当组件单独失活时不受影响,其它情况协程会停止
#endregion
}
void Update()
{
//在主线程判断 副线程有没有往队列里放东西,有的话就拿出来用
if (queue.Count > 0)
{
//取出位置
this.transform.position = queue.Dequeue();
}
}
//申明协程函数
//关键点一: 协同程序(协程)函数 返回值 必须是 IEnumerator或者继承它的类型
IEnumerator MyCoroutine(int i, string str)
{
//关键点二: 协程函数当中 必须使用 yield return 进行返回
#region 知识点五 yield return 不同内容的含义
//1.下一帧执行
//yield return 数字;
//yield return null;
//在Update和LateUpdate之间执行
//2.等待指定秒后执行
//yield return new WaitForSeconds(秒);
//在Update和LateUpdate之间执行
//3.等待下一个固定物理帧更新时执行
//yield return new WaitForFixedUpdate();
//在FixedUpdate和碰撞检测相关函数之后执行
//4.等待摄像机和GUI渲染完成后执行
//yield return new WaitForEndOfFrame();
//在LateUpdate之后的渲染相关处理完毕后之后
//5.一些特殊类型的对象 比如异步加载相关函数返回的对象
//之后讲解 异步加载资源 异步加载场景 网络加载时再讲解
//一般在Update和LateUpdate之间执行
//6.跳出协程
//yield break;
#endregion
print("传进来的int值" + i);
print("yield return 任意数字 或 yield return null");
print("下一帧后在Update和LateUpdate之间再执行后面的逻辑");
yield return 123;
yield return null;
print("传进来的string值" + str);
print("yield return new WaitForSeconds(3f)");
print("等待3秒后在Update和LateUpdate之间再执行后面的逻辑");
yield return new WaitForSeconds(3f);
print("WaitForFixedUpdate()");
print("等待下一个固定物理帧更新时再执行后面的逻辑");
yield return new WaitForFixedUpdate();
print("WaitForEndOfFrame()");
print("等待摄像机和GUI渲染完成后再执行后面的逻辑");
//主要会用来 截图时 会使用 截图一般是要渲染完再截图
yield return new WaitForEndOfFrame();
print("跳出协程");
//注意:直接跳出协程后再StopCoroutine(Coroutine协程对象),会报错,跳出后代表这个协程已经结束了
yield break;
//协程函数是可以写死循环的
while (true)
{
print("死循环内 yield return new WaitForSeconds(5f);");
print("死循环内 等待5秒后在Update和LateUpdate之间再执行后面的逻辑");
yield return new WaitForSeconds(5f);
}
}
//新线程逻辑
private void newThreadLogic()
{
while (true)
{
Thread.Sleep(1000);
print("新线程逻辑 每隔一秒的打印");
//我们在Unity中,不会开启多线程访问Unity相关
//当射涉及复杂逻辑的计算的时候,假如都放到主线程里,可能会造成主线程的卡顿
//这时就可以开启多线程用于A星寻路算法,网络收发相关
//当算好了结果或者收到了消息过后,放在公共的内存区域
//主线程判断判断公共的内存区域有没有想要的对象,拿来使用
//这句代码会报错 不能在副线程控制Unity相关
//UnityException: get_transform can only be called from the main thread.
//this.transform.Translate(Vector3.forward * Time.deltaTime);
//相当于模拟 复杂算法 算出了一个结果 然后放入公共容器中
//用Unity的随机数都不能用 用C#里的随机数
System.Random r = new System.Random();
queue.Enqueue(new Vector3(r.Next(-10, 10), r.Next(-10, 10), r.Next(-10, 10)));
}
}
private void OnDestroy()
{
//关闭线程
newThread.Abort();
//关闭线程后置空
newThread = null;
}
}
15.3 练习题
利用协程制作一个计秒器
StartCoroutine(MyTimerCoroutine());
// 我的计时器协程
IEnumerator MyTimerCoroutine()
{
int time = 0;
// 计时死循环
while (true)
{
print("我的计时器" + time + "秒");
++time;
// 每过一秒再执行
yield return new WaitForSeconds(1);
}
}
在场景中创建100000个随机位置的立方体,让其不会明显卡顿
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
StartCoroutine(CreateCube(100000));
}
}
// 创建立方体的协程函数
IEnumerator CreateCube(int num)
{
for (int i = 0; i < num; i++)
{
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj.transform.position = new Vector3(Random.Range(-100, 100), Random.Range(-100, 100), Random.Range(-100, 100));
// 每创建1000个,就下一帧再创建,这样就没那么卡
if (i % 1000 == 0)
yield return null;
}
}
15.4 练习题代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson15_练习题 : MonoBehaviour
{
void Start()
{
#region 练习题一
//利用协程制作一个计秒器
StartCoroutine(MyTimerCoroutine());
#endregion
#region 练习题二
//请在场景中创建100000个随机位置的立方体,让其不会明显卡顿
#endregion
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
//直接创建10万个肯定卡
//for (int i = 0; i < 100000; i++)
//{
// GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
// obj.transform.position = new Vector3(Random.Range(-100, 100), Random.Range(-100, 100), Random.Range(-100, 100));
//}
StartCoroutine(CreateCube(100000));
}
}
//创建立方体的协程函数
IEnumerator CreateCube(int num)
{
for (int i = 0; i < num; i++)
{
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj.transform.position = new Vector3(Random.Range(-100, 100), Random.Range(-100, 100), Random.Range(-100, 100));
//每创建1000个 就下一帧再创建 这样就没那么卡
if (i % 1000 == 0)
yield return null;
}
}
//我的计时器协程
IEnumerator MyTimerCoroutine()
{
int time = 0;
//计时死循环
while (true)
{
print("我的计时器"+time + "秒");
++time;
//每过一秒再执行
yield return new WaitForSeconds(1);
}
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com