前言:

协程在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. Aliyun Oss 上传文件

    目录 Aliyun OSS OSS 简介 OSS 基本概念 OSS 功能概述 OSS 使用 创建存储空间Bucket 创建子目录 Java编码 测试 Aliyun OSS OSS 简介 阿里云对象存储 ...

  2. 后端程序员之路 4、一种monitor的做法

    record_t包含_sum._count._time_stamp._max._min最基础的一条记录,可以用来记录最大值.最小值.计数.总和metric_t含有RECORD_NUM(6)份recor ...

  3. C#.NET操作数据库通用类

    下面给出了一个C#操作MS SQL Server 数据库的通用类,通过该类可以对数据库进行任何操作,包括执行SQL语句.执行存储过程.以下是其详细实现过程,希望大家共同修改优化之.稍后将介绍如何使用它 ...

  4. PyQt5之 QTableView 添加复选框(自定义委托)

    import sys from untitled import Ui_Form from PyQt5.QtWidgets import QApplication, QWidget, QStyleOpt ...

  5. kubernetes Pod亲和性

    三种调度粘性,主要根据官方文档说明: NodeSelector(定向调度).NodeAffinity(Node亲和性).PodAffinity(Pod亲和性). 1.      nodeSelecto ...

  6. Java 开发工具

    开发工具·Eclipse 常见开发工具介绍 * A:操作系统自带的记事本软件 * B:高级记事本软件 * C:集成开发环境 IDE     * (Integrated Development Envi ...

  7. Fedora一键安装NVIDIA显卡驱动Fedora28+

    这是一篇以前写的文章,写在CSDN的,现在不想使用CSDN了,就把笔记写在了博客源,后续考虑建立自己的博客,每一个CRUD程序员都想建立自己的博客吧,我猜是的 进入正题 rpm fusion源包含Nv ...

  8. Mongo的相关语法

    mongod的条件操作符 $gt -------- greater than > $gte --------- gt equal >= $lt -------- less than < ...

  9. Java基础:重文本markdown

    说一说markdown 前言 在系统学习Java等开发语言之前,我们应该养成写"日记"的习惯,也就是写博客:写博客的地方有博客园,csdn等.而写博客又得知道markdown重文本 ...

  10. 【数据结构与算法】——队列(Queue)

    队列(Queue)的一个使用场景 银行排队的案例: 队列(Queue)介绍 队列是一个有序列表,可以用数组或是链表来实现. 遵循先入先出的原则.即:先存入队列的数据,要先取出来.后存入的要后取出来. ...