Unity 中协程是个非常强大的功能,其作用主要是用于游戏中的延时调用或者执行一连串的有时间间隔的事件流程,例如剧情对话等。简单总结了几点协程相关的知识点,旨在加深记忆,同时为初学者解惑。

1、协程、进程与线程

这是个面试中经常会问到的问题:协程、进程与线程的区别在哪?

  说到协程,我们首先回顾以下线程与进程这两个概念。在操作系统(os)级别,有进程(process)和线程(thread)两个我们看不到但又实际存在的“东西”,这两个东西都是用来模拟“并行”的,写操作系统的程序员通过用一定的策略给不同的进程和线程分配CPU计算资源,来让用户“以为”几个不同的事情在“同时”进行“。在单CPU上,是os代码强制把一个进程或者线程挂起,换成另外一个来计算,所以,实际上是串行的,只是“概念上的并行”。在现在的多核的cpu上,线程可能是“真正并行的”。

进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。

线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。

协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。

一个应用程序一般对应一个进程,一个进程一般有一个主线程,还有若干个辅助线程,线程之间是平行运行的,在线程里面可以开启协程,让程序在特定的时间内运行。

协程和线程的区别是:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。

打个比方吧,假设有一个操作系统,是单核的,系统上没有其他的程序需要运行,有两个线程 A 和 B ,A 和 B 在单独运行时都需要 10 秒来完成自己的任务,而且任务都是运算操作,A B 之间也没有竞争和共享数据的问题。现在 A B 两个线程并行,操作系统会不停的在 A B 两个线程之间切换,达到一种伪并行的效果,假设切换的频率是每秒一次,切换的成本是 0.1 秒(主要是栈切换),总共需要 20 + 19 * 0.1 = 21.9 秒。如果使用协程的方式,可以先运行协程 A ,A 结束的时候让位给协程 B ,只发生一次切换,总时间是 20 + 1 * 0.1 = 20.1 秒。如果系统是双核的,而且线程是标准线程,那么 A B 两个线程就可以真并行,总时间只需要 10 秒,而协程的方案仍然需要 20.1 秒。

  其实就根本来说,协程除了名字之外,和线程是没有太大联系的。Unity中的特殊在于所有的脚本和代码都是在一个主线程里运行的,协程也不例外。协程与线程的相似点只在于,协程看起来也可以与其他函数并行执行。 但本质上来说,线程是通过可以开启多个子线程同时执行程序,而达到并行。而协程则是通过每帧检测的方式,在自己与其他函数之间切换。这种“来回跑”的方式,与Unity中一惯有明确执行顺序的风格(脚本生命周期)看起来不太统一。但这正是它的强大之处,使得我们在使用协程的时候不必考虑lock等诸多线程中的问题。

2、协程执行原理

unity中协程执行过程中,通过yield return XXX,将程序挂起,去执行接下来的内容,注意协程不是线程,在为遇到yield return XXX语句之前,协程额方法和一般的方法是相同的,也就是程序在执行到yield return XXX语句之后,接着才会执行的是 StartCoroutine()方法之后的程序,走的还是单线程模式,仅仅是将yield return XXX语句之后的内容暂时挂起,等到特定的时间才执行。
那么挂起的程序什么时候才执行,这就要看monoBehavior的生命周期了。

也就是协同程序主要是update()方法之后,lateUpdate()方法之前调用的,接下来我们通过一个小例子去理解一下。

using UnityEngine;
using System.Collections;
using System.Threading;
public class test : MonoBehaviour
{ void Start()
{
StartCoroutine(tt());//开启协程
for (int i = ; i < ; i++) //循环A
{
Debug.Log("*************************" + i);
Thread.Sleep();
}
} IEnumerator tt()
{
for (int i = ; i < ; i++) //循环B
{
Debug.Log("-------------------" + i);
} yield return new WaitForSeconds(); //协程1 for (int i = ; i < ; i++) //循环C
{
Debug.Log(">>>>>>>>>>>>>>>>>>>>" + i);
yield return null; //协程1
}
} // 更新数据
void Update()
{
Debug.Log("Update");
} //晚于更新
void LateUpdate()
{
Debug.Log("------LateUpdate");
} }

程序的运行结果为:

先执行循环B,然后执行循环A,然后执行update()和lateUpdate()的方法,等待1S之后,在updat()和lateupda()之间执行循环C的输出。

3、yield return 的不同返回类型

  使用yield return的时候你会发现它可以返回的类型一长串,对于初学者我觉得就分为带 new和不带new的就行了。

  先说不带new的。通常可以yield return的有 null,数字 ,字符串,布尔值甚至表达式,函数,嵌套协程等。

  以在Start()中开启当前协程为例,如果是不带new的返回类型,执行时间都是一样的。即在第一时间执行协程中的代码 到第一个yield return当行为止,然后在下一帧的Update之后,LateUpdate之前执行yield return后面的代码。

  另外需要注意的是,yield return后面可以是一个函数调用,赋值表达式,嵌套的其它协程等。以赋值的表达式num=10为例;它会在当行yield return执行的时候就执行,函数调用和其它协程也是一样。也就是说,此时yield return的函数调用就相当于直接调用了这个函数,并且是当时就执行的。 而其它return 类型 如null,字符串,数字等一般只用作延迟一帧来用,其它作用,待我后期再研究下。

  下面说带new的,也是通常我们重点使用的协程功能。

   这里列举几个:

  (1)new  WaitUntil(Func<bool>)  参数是一个布尔返回类型的委托,作用是,知道这个返回的布尔值为true时,协程才会继续执行当行yield return 后面的代码。

  (2) new WaitForSeconds(float)参数是float类型的数字,表示秒,也是协程最常用的功能之一。 作用是,在N秒后才会继续执行当行yield return 后面的代码。

由于yield return可以在一个协程中任意位置写多个,配合这个可以实现很多时间细化可视化的功能。

  (3)new WaitForEndOfFrame()作用是,在结束当前帧 摄像机和GUI被渲染以及其它函数完成后才会继续执行当行yield return 后面的代码。 这个我只验证了在LateUpdate执行完之后执行,具体在整个脚本周期中哪个函数执行完之后开始执行还未详细验证。

  (4)new  WaitForFixedUpdate()  作用是,直到当行代码之后第一个FixedUpdate执行之后才会继续执行当行yield return 后面的代码。也就是说,如果是在start里面开启协程的话,第一次执行FixedUpdate之后就会继续执行return后面的代码。

  后面还有许多类型的 返回,没有一一验证,不过作用应该大同小异,即在执行第一个该类型动作之后才会继续执行当行yield return 后面的代码。

  值得一提的是,协程的延迟调用和非阻塞式挂起是用于网络请求等高级结构很好的工具,非常值得花一些时间去仔细研究。

4、开始协程

  通过MonoBehaviour提供的StartCoroutine方法来实现启动协同程序。

  1、StartCoroutine(IEnumerator routine);

  优点:灵活,性能开销小。

  缺点:无法单独的停止这个协程,如果需要停止这个协程只能等待协同程序运行完毕或则使用StopAllCoroutine();方法。

  2、StartCoroutine (methodName:string, value : object = null);

  优点:可以直接通过传入协同程序的方法名来停止这个协程:StopCoroutine(string methodName);

  缺点:性能的开销较大,只能传递一个参数。

5、停止协程

  协程内停止可以用yield return break;

  协程外:

  1、StopCoroutine(string methodName);

  2、StopAllCoroutine();

  3、设置gameobject的active为false时可以终止协同程序,但是再次设置为true后协程不会再启动。设置当前协程所在脚本enable为false也并不能停止当前协程的执行。

Unity Coroutine详解(一)的更多相关文章

  1. Unity Coroutine详解(二)

    •        介绍•        Part 1. 同步等待•        Part 2. 异步协程•        Part 3. 同步协程•        Part 4. 并行协程 1.介绍 ...

  2. Unity Jobsystem 详解实体组件系统ECS

    原文摘选自Unity Jobsystem 详解实体组件系统ECS 简介 随着ECS的加入,Unity基本上改变了软件开发方面的大部分方法.ECS的加入预示着OOP方法的结束.随着实体组件系统ECS的到 ...

  3. Unity 灯光系统详解

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

  4. Lua Coroutine详解

    协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈,局部变量和指令指针,同时又与其它协同程序共享全局变量和其它大部分东西.线程与协同程序的主要区别在于,一个具有多线程的程序可以同时运行几个线程 ...

  5. Unity3D中的Coroutine详解

    Unity中的coroutine是通过yield expression;来实现的.官方脚本中到处会看到这样的代码. 疑问: yield是什么? Coroutine是什么? unity的coroutin ...

  6. 【Unity3D/C#】Unity3D中的Coroutine详解

    Unity中的coroutine是通过yield expression;来实现的.官方脚本中到处会看到这样的代码. 疑问: yield是什么? Coroutine是什么? unity的coroutin ...

  7. Tornado中gen.coroutine详解

    1.gen.coroutine的作用 自动执行生成器 2.Future对象 在介绍异步使用之前,先了解一下Future对象的作用. Future简单可以理解为一个占位符,将来会执行的对象,类似java ...

  8. Unity SurfaceShader详解

    声明:文章主要是总结手游开发的经验,只涉及到了前向渲染.未涉及延迟渲染. Unity的Surface Shader本质上就是VS/PS.只不过Unity经过精心设计,将shader划分为了几个关键部分 ...

  9. Unity灯光详解

    Lights will bring personality and flavor to your game. You use lights to illuminate the scenes and o ...

随机推荐

  1. Linux shell 只删除目录下所有(不知道文件名字)文件,只删除文件夹

    #!/bin/sh RM="rm -rf" function delete_all_dir() { for i in `ls` do if [ -d $i ];then $RM $ ...

  2. Linux服务器部署.Net Core笔记:目录

        目录 Linux服务器部署.Net Core笔记:一.开启ssh服务 Linux服务器部署.Net Core笔记:二.安装FTP Linux服务器部署.Net Core笔记:三.安装.NetC ...

  3. 简单scrapy爬虫实例

    简单scrapy爬虫实例 流程分析 抓取内容:网站课程 页面:https://edu.hellobi.com 数据:课程名.课程链接及学习人数 观察页面url变化规律以及页面源代码帮助我们获取所有数据 ...

  4. 数据结构KMP算法中手算next数组

    总结一下今天的收获(以王道数据结构书上的为例子,虽然我没看它上面的...):其中竖着的一列值是模式串前缀和后缀最长公共前缀. 最后求得的结果符合书上的结果,如果是以-1开头的话就不需要再加1,如果是以 ...

  5. Selenium3+python自动化013-操作浏览器的Cookie

    为什么要用Cookie?在测试多个页面时候可绕过验证码输入,直接添加cookie,也可以在添加唯一标识时候使用. 一.操作浏览器的Cookie 1.1.验证码的处理方式 说明:WebDriver类库中 ...

  6. 我的python笔记05

    Python 之路 Day5 - 常用模块学习 本节大纲: 模块介绍 time &datetime模块 random os sys shutil json & picle shelve ...

  7. Java学习笔记(十二)面向对象---内部类

    内部类的访问规则 内部类可以直接访问外部类中的成员,包括私有成员. 因为内部类中持有了一个外部类的引用,格式为:外部类名.this 外部类要访问内部类,必须要建立内部对象. class Outer { ...

  8. promise的连缀写法

    promise的连缀写法 以上写法相当于写了两个实例 promise.all() 1. promise.all() all这个方法是 promise 构造函数的成员不是实例对象成员,这个方法接受一个参 ...

  9. rest_framework:响应器(渲染器)

    一.作用: 根据用户的请求url或者用户可接受的类型.筛选出合适的渲染组件 用户请求url: http://127.0.0.1:8000/test/?format=json http://127.0. ...

  10. 【Unity|C#】基础篇(6)——const、readonly、static readonly

    [学习资料] <C#图解教程>(第6章):https://www.cnblogs.com/moonache/p/7687551.html 电子书下载:https://pan.baidu.c ...