前言:

协程在Unity中是一个很重要的概念,我们知道,在使用Unity进行游戏开发时,一般(注意是一般)不考虑多线程,那么如何处理一些在主任务之外的需求呢,Unity给我们提供了协程这种方式

为啥在Unity中一般不考虑多线程

  • 因为在Unity中,只能在主线程中获取物体的组件、方法、对象,如果脱离这些,Unity的很多功能无法实现,那么多线程的存在与否意义就不大了

既然这样,线程与协程有什么区别呢:

  • 对于协程而言,同一时间只能执行一个协程,而线程则是并发的,可以同时有多个线程在运行
  • 两者在内存的使用上是相同的,共享堆,不共享栈

其实对于两者最关键,最简单的区别是微观上线程是并行的,而协程是串行的,如果你不理解没有关系,通过下面的解释你就明白了

关于协程

1,什么是协程

协程,从字面意义上理解就是协助程序的意思,我们在主任务进行的同时,需要一些分支任务配合工作来达到最终的效果

稍微形象的解释一下,想象一下,在进行主任务的过程中我们需要一个对资源消耗极大的操作时候,如果在一帧中实现这样的操作,游戏就会变得十分卡顿,这个时候,我们就可以通过协程,在一定帧内完成该工作的处理,同时不影响主任务的进行

2,协程的原理

首先需要了解协程不是线程,协程依旧是在主线程中进行

然后要知道协程是通过迭代器来实现功能的,通过关键字IEnumerator来定义一个迭代方法,注意使用的是IEnumerator,而不是IEnumerable:

两者之间的区别:

  • IEnumerator:是非泛型的,也是协程认可的参数
  • IEnumerable:通过泛型实现的迭代器,协程不使用该迭代器

在迭代器中呢,最关键的是yield 的使用,这是实现我们协程功能的主要途径,通过该关键方法,可以使得协程的运行暂停、记录下一次启动的时间与位置等等:

关于迭代器的具体解释:

由于yield 在协程中的特殊性,与关键性,我们到后面在单独解释,先介绍一下协程如何通过代码实现

3、协程的使用

首先通过一个迭代器定义一个返回值为IEnumerator的方法,然后再程序中通过StartCoroutine来开启一个协程即可:

在正式开始代码之前,需要了解StartCoroutine的两种重载方式:

  • StartCoroutine(string methodName:这种是没有参数的情况,直接通过方法名(字符串形式)来开启协程
  • StartCoroutine(IEnumerator routine:通过方法形式调用
  • StartCoroutine(string methodName,object values):带参数的通过方法名进行调用

协程开启的方式主要是上面的三种形式,如果你还是不理解,可以查看下面代码:

 	//通过迭代器定义一个方法
IEnumerator Demo(int i)
{
//代码块 yield return 0;
//代码块 } //在程序种调用协程
public void Test()
{
//第一种与第二种调用方式,通过方法名与参数调用
StartCoroutine("Demo", 1); //第三种调用方式, 通过调用方法直接调用
StartCoroutine(Demo(1));
}

在一个协程开始后,同样会对应一个结束协程的方法StopCoroutineStopAllCoroutines两种方式,但是需要注意的是,两者的使用需要遵循一定的规则,在介绍规则之前,同样介绍一下关于StopCoroutine重载:

  • StopCoroutine(string methodName:通过方法名(字符串)来进行
  • StopCoroutine(IEnumerator routine:通过方法形式来调用
  • StopCoroutine(Coroutine routine):通过指定的协程来关闭

刚刚我们说到他们的使用是有一定的规则的,那么规则是什么呢,答案是前两种结束协程方法的使用上,如果我们是使用StartCoroutine(string methodName)来开启一个协程的,那么结束协程就只能使用StopCoroutine(string methodName)StopCoroutine(Coroutine routine)来结束协程,可以在文档中找到这句话:

4、关于yield

在上面,我们已经知道yield 的关键性,要想理解协程,就要理解yield

如果你了解Unity的脚本的生命周期,你一定对yield 这几个关键词很熟悉,没错,yield 也是脚本生命周期的一些执行方法,不同的yield 的方法处于生命周期的不同位置,可以通过下图查看:



通过这张图可以看出大部分yield位置UpdateLateUpdate之间,而一些特殊的则分布在其他位置,这些yield 代表什么意思呢,又为啥位于这个位置呢


首先解释一下位于UpdateLateUpdate之间这些yield 的含义:

  • yield return null; 暂停协程等待下一帧继续执行

  • yield return 0或其他数字; 暂停协程等待下一帧继续执行

  • yield return new WairForSeconds(时间); 等待规定时间后继续执行

  • yield return StartCoroutine("协程方法名");开启一个协程(嵌套协程)

在了解这些yield的方法后,可以通过下面的代码来理解其执行顺序:

 void Update()
{
Debug.Log("001");
StartCoroutine("Demo");
Debug.Log("003"); }
private void LateUpdate()
{
Debug.Log("005");
} IEnumerator Demo()
{
Debug.Log("002"); yield return 0;
Debug.Log("004");
}

将上面的脚本挂载到物体上,运行游戏场景,来查看打印的日志,可以看到下面的日志记录:



可以很清晰的看出,协程虽然是在Update中开启,但是关于yield return null后面的代码会在下一帧运行,并且是在Update执行完之后才开始执行,但是会在LateUpdate之前执行


接下来看几个特殊的yield,他们是用在一些特殊的区域,一般不会有机会去使用,但是对于某些特殊情况的应对会很方便

  • yield return GameObject; 当游戏对象被获取到之后执行
  • yield return new WaitForFixedUpdate():等到下一个固定帧数更新
  • yield return new WaitForEndOfFrame():等到所有相机画面被渲染完毕后更新
  • yield break; 跳出协程对应方法,其后面的代码不会被执行

通过上面的一些yield一些用法以及其在脚本生命周期中的位置,我们也可以看到关于协程不是线程的概念的具体的解释,所有的这些方法都是在主线程中进行的,只是有别于我们正常使用的UpdateLateUpdate这些可视的方法

5、线程几个小用法

5.1、将一个复杂程序分帧执行:

如果一个复杂的函数对于一帧的性能需求很大,我们就可以通过yield return null将步骤拆除,从而将性能压力分摊开来,最终获取一个流畅的过程,这就是一个简单的应用

举一个案例,如果某一时刻需要使用Update读取一个列表,这样一般需要一个循环去遍历列表,这样每帧的代码执行量就比较大,就可以将这样的执行放置到协程中来处理:

public class Test : MonoBehaviour
{
public List<int> nums = new List<int> { 1, 2, 3, 4, 5, 6 }; private void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
StartCoroutine(PrintNum(nums));
}
}
//通过协程分帧处理
IEnumerator PrintNum(List<int> nums)
{
foreach(int i in nums)
{
Debug.Log(i);
yield return null; } }
}

上面只是列举了一个小小的案例,在实际工作中会有一些很消耗性能的操作的时候,就可以通过这样的方式来进行性能消耗的分消

5.2、进行计时器工作

当然这种应用场景很少,如果我们需要计时器有很多其他更好用的方式,但是你可以了解是存在这样的操作的,要实现这样的效果,需要通过yield return new WaitForSeconds()的延时执行的功能:

	IEnumerator Test()
{
Debug.Log("开始");
yield return new WaitForSeconds(3);
Debug.Log("输出开始后三秒后执行我");
}

5.3、异步加载等功能

只要一说到异步,就必定离不开协程,因为在异步加载过程中可能会影响到其他任务的进程,这个时候就需要通过协程将这些可能被影响的任务剥离出来

常见的异步操作有:

  • AB包资源的异步加载
  • Reaources资源的异步加载
  • 场景的异步加载
  • WWW模块的异步请求

这些异步操作的实现都需要协程的支持,可以通过我之前的一篇场景加载界面实现的文章来理解该内容:

关于异步的文章:

总结

通过上面的一些操作,相信你应该理解协程的基本原理与用法,以及一些相关的小知识

因为协程本身也是一个比较复杂的概念,所以我的理解也可能有错误的地方,如果你发现文章中有哪些不正确的地方,欢迎留言指出< ^ _ ^ >

Unity 协程(Coroutine)原理与用法详解的更多相关文章

  1. Unity协程(Coroutine)管理类——TaskManager工具分享

    博客分类: Unity3D插件学习,工具分享 源码分析   Unity协程(Coroutine)管理类——TaskManager工具分享 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处 ...

  2. Unity协程Coroutine使用总结和一些坑

    原文摘自 Unity协程Coroutine使用总结和一些坑 MonoBehavior关于协程提供了下面几个接口: 可以使用函数或者函数名字符串来启动一个协程,同时可以用函数,函数名字符串,和Corou ...

  3. unity协程coroutine浅析

    转载请标明出处:http://www.cnblogs.com/zblade/ 一.序言 在unity的游戏开发中,对于异步操作,有一个避免不了的操作: 协程,以前一直理解的懵懵懂懂,最近认真充电了一下 ...

  4. Unity 协程Coroutine综合测试

    using UnityEngine; using System.Collections; using System.Text; public class rotCube : MonoBehaviour ...

  5. Kotlin协程作用域与构建器详解

    在上次我们是通过了这种方式来创建了一个协程: 接着再来看另一种创建协程的方式: 下面用它来实现上一次程序一样的效果,先来回顾一下上一次程序的代码: 好,下面改用runBlocking的方式: 运行一下 ...

  6. Python函数装饰器原理与用法详解《摘》

    本文实例讲述了Python函数装饰器原理与用法.分享给大家供大家参考,具体如下: 装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值 ...

  7. Unity 3D光源-Spot Light聚光灯用法详解、模拟手电筒、台灯等线性教程

    Unity4大光源之聚光灯 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar -- 心分享. ...

  8. ThreadLocal原理及用法详解

    背景 一直以来对ThreadLocal用法模棱两可,不知道怎么用今天好好研究了下给大家分享下. 1.讲解ThreadLocal之前先回顾下什么是取模.x^y.弱引用. 1. 取模运算实际上是计算两数相 ...

  9. xargs原理及用法详解

    为什么需要xargs 管道实现的是将前面的stdout作为后面的stdin,但是有些命令不接受管道的传递方式,最常见的就是ls命令.有些时候命令希望管道传递的是参数,但是直接用管道有时无法传递到命令的 ...

随机推荐

  1. SpringBoot读取配置文件的内容

    1.@Value读取 在springboot项目中,如果要读取配置文件application.properties或application.yml文件的内容,可以使用自带的注解@Value.以prop ...

  2. Innodb的存储及缓存

    参考[mysql技术内幕] 一.mysql体系结构和存储引擎 1.数据库与数据库实例 数据库:物理操作系统文件或者其他文件组成的集合: 数据库实例:有数据库后台进程/线程和一个共享内存区域组成. 数据 ...

  3. C++中tuple类型

    tuple是C++11新标准里的类型.它是一个类似pair类型的模板.pair类型是每个成员变量各自可以是任意类型,但是只能有俩个成员,而tuple与pair不同的是它可以有任意数量的成员.但是每个确 ...

  4. WIFI6 基本知识(一)

    什么是WI-FI6(802.11ax) Wi-Fi 6 是下一代 802.11ax 标准的简称.随着 Wi-Fi 标准的演进,WFA 为了便于 Wi-Fi 用户和设备厂商轻松了解其设备连接或支持的 W ...

  5. JVM-对象及对象内存布局

    目录 前言 类与对象 对象类二分模型 对象 对象内存布局 JOL工具 对象头 Mark Word 类型句柄 对象头与锁膨胀 无锁 偏向锁 轻量级锁 重量级锁 重量级锁降级 实例数据 填充 对象生命周期 ...

  6. xss和实体编码的一点小思考

    首先,浏览器渲染分以下几步: 解析HTML生成DOM树. 解析CSS生成CSSOM规则树. 将DOM树与CSSOM规则树合并在一起生成渲染树. 遍历渲染树开始布局,计算每个节点的位置大小信息. 将渲染 ...

  7. Celery:小试牛刀

    Celery是如何工作的? Celery 由于 其分布式体系结构,在某种程度上可能难以理解.下图是典型Django-Celery设置的高级示意图(FROM O'REILLY): 当请求到达时,您可以在 ...

  8. 我给Apache顶级项目贡献了点源码。

    这是why技术的第 91 篇原创文章 这篇文章其实并没有什么技术性的分享,从我的角度而言,更多是记录和思考. 把我对于源码和之前写的部分文章反哺给我的一些东西,带来的一点点思考分享给大家. 一行源码 ...

  9. Java volatile关键字详解

    Java volatile关键字详解 volatile是java中的一个关键字,用于修饰变量.被此关键修饰的变量可以禁止对此变量操作的指令进行重排,还有保持内存的可见性. 简言之它的作用就是: 禁止指 ...

  10. OpenCV 之 角点检测

    角点 (corners) 的定义有两个版本:一是 两条边缘的交点,二是 邻域内具有两个主方向的特征点. 一般而言,角点是边缘曲线上曲率为极大值的点,或者 图像亮度发生剧烈变化的点.例如,从人眼角度来看 ...