深圳 网站制作需要多少钱 网络服务,phpstudy如何建设网站,简历电子版模板免费下载,许昌市网站开发参考链接#xff1a;Unity 协程(Coroutine)原理与用法详解_unity coroutine-CSDN博客 为啥在Unity中一般不考虑多线程 因为在Unity中#xff0c;只能在主线程中获取物体的组件、方法、对象#xff0c;如果脱离这些#xff0c;Unity的很多功能无法实现#xff0c;那么多线程…参考链接Unity 协程(Coroutine)原理与用法详解_unity coroutine-CSDN博客 为啥在Unity中一般不考虑多线程 因为在Unity中只能在主线程中获取物体的组件、方法、对象如果脱离这些Unity的很多功能无法实现那么多线程的存在与否意义就不大了 既然这样线程与协程有什么区别呢
对于协程而言同一时间只能执行一个协程而线程则是并发的可以同时有多个线程在运行两者在内存的使用上是相同的共享堆不共享栈
其实对于两者最关键最简单的区别是微观上线程是并行对于多核CPU的而协程是串行的如果你不理解没有关系通过下面的解释你就明白了
关于协程 1什么是协程 协程从字面意义上理解就是协助程序的意思我们在主任务进行的同时需要一些分支任务配合工作来达到最终的效果
稍微形象的解释一下想象一下在进行主任务的过程中我们需要一个对资源消耗极大的操作时候如果在一帧中实现这样的操作游戏就会变得十分卡顿这个时候我们就可以通过协程在一定帧内完成该工作的处理同时不影响主任务的进行
2协程的原理 首先需要了解协程不是线程协程依旧是在主线程中进行
然后要知道协程是通过迭代器来实现功能的通过关键字IEnumerator来定义一个迭代方法注意使用的是IEnumerator而不是IEnumerable 两者之间的区别 IEnumerator是一个实现迭代器功能的接口IEnumerable是在IEnumerator基础上的一个封装接口有一个GetEnumerator()方法返回IEnumerator 3、协程的使用
首先通过一个迭代器定义一个返回值为IEnumerator的方法然后再程序中通过StartCoroutine来开启一个协程即可 //通过迭代器定义一个方法IEnumerator Demo(int i){//代码块yield return 0; //代码块}//在程序种调用协程public void Test(){//第一种与第二种调用方式,通过方法名与参数调用StartCoroutine(Demo, 1);//第三种调用方式 通过调用方法直接调用StartCoroutine(Demo(1));}在一个协程开始后同样会对应一个结束协程的方法StopCoroutine与StopAllCoroutines两种方式但是需要注意的是两者的使用需要遵循一定的规则在介绍规则之前同样介绍一下关于StopCoroutine重载
StopCoroutinestring methodName通过方法名字符串来进行StopCoroutineIEnumerator routine:通过方法形式来调用StopCoroutine(Coroutine routine)通过指定的协程来关闭
刚刚我们说到他们的使用是有一定的规则的那么规则是什么呢答案是前两种结束协程方法的使用上如果我们是使用StartCoroutinestring methodName来开启一个协程的那么结束协程就只能使用StopCoroutinestring methodName和StopCoroutine(Coroutine routine)来结束协程可以在文档中找到这句话 Unity生命周期 首先解释一下位于Update与LateUpdate之间这些yield 的含义
yield return null; 暂停协程等待下一帧继续执行yield return 0或其他数字; 暂停协程等待下一帧继续执行yield return new WairForSeconds(时间); 等待规定时间后继续执行yield return StartCoroutine(协程方法名);开启一个协程嵌套协程)
接下来看几个特殊的yield他们是用在一些特殊的区域一般不会有机会去使用但是对于某些特殊情况的应对会很方便
yield return GameObject; 当游戏对象被获取到之后执行yield return new WaitForFixedUpdate()等到下一个固定帧数更新yield return new WaitForEndOfFrame():等到所有相机画面被渲染完毕后更新yield break; 跳出协程对应方法其后面的代码不会被执行
通过上面的一些yield一些用法以及其在脚本生命周期中的位置我们也可以看到关于协程不是线程的概念的具体的解释所有的这些方法都是在主线程中进行的只是有别于我们正常使用的Update与LateUpdate这些可视的方法
5、协程几个小用法
5.1、将一个复杂程序分帧执行
如果一个复杂的函数对于一帧的性能需求很大我们就可以通过yield return null将步骤拆除从而将性能压力分摊开来最终获取一个流畅的过程这就是一个简单的应用
5.3、异步加载等功能
只要一说到异步就必定离不开协程因为在异步加载过程中可能会影响到其他任务的进程这个时候就需要通过协程将这些可能被影响的任务剥离出来
常见的异步操作有
AB包资源的异步加载Reaources资源的异步加载场景的异步加载WWW模块的异步请求 参考链接迭代器 - C# | Microsoft Learn
可根据需要提供尽可能多的 yield return 语句来满足方法需求
public IEnumerableint GetSetsOfNumbers()
{int index 0;while (index 10)yield return index;yield return 50;index 100;while (index 110)yield return index;
}
上述所有示例都有一个异步对应项。 在每种情况下将 IEnumerableT 的返回类型替换为 IAsyncEnumerableT。 例如前面的示例将具有以下异步版本
public async IAsyncEnumerableint GetSetsOfNumbersAsync()
{int index 0;while (index 10)yield return index;await Task.Delay(500);yield return 50;await Task.Delay(500);index 100;while (index 110)yield return index;
}
迭代器方法有一个重要限制在同一方法中不能同时使用 return 语句和 yield return 语句。 以下代码无法编译
有时正确的做法是将迭代器方法拆分成 2 个不同的方法。 一个使用 return另一个使用 yield return。 考虑这样一种情况需要基于布尔参数返回一个空集合或者返回前 5 个奇数。 可编写类似以下 2 种方法的方法
public IEnumerableint GetSingleDigitOddNumbers(bool getCollection)
{if (getCollection false)return new int[0];elsereturn IteratorMethod();
}private IEnumerableint IteratorMethod()
{int index 0;while (index 10){if (index % 2 1)yield return index;index;}
}
看看上面的方法。 第 1 个方法使用标准 return 语句返回空集合或返回第 2 个方法创建的迭代器。 第 2 个方法使用 yield return 语句创建请求的序列。 参考链接Unity协程的原理与应用 - 知乎 (zhihu.com) A coroutine is like a function that has the ability to pause execution and return control to Unity but then to continue where it left off on the following frame. By default, a coroutine is resumed on the frame after it yields but it is also possible to introduce a time delay using [WaitForSeconds](https://docs.unity3d.com/ScriptReference/WaitForSeconds.html) 简单的说协程就是一种特殊的函数它可以主动的请求暂停自身并提交一个唤醒条件Unity会在唤醒条件满足的时候去重新唤醒协程。
2. 如何使用
MonoBehaviour.StartCoroutine()方法可以开启一个协程这个协程会挂在该MonoBehaviour下。
在MonoBehaviour生命周期的Update和LateUpdate之间会检查这个MonoBehaviour下挂载的所有协程并唤醒其中满足唤醒条件的协程。
要想使用协程只需要以IEnumerator为返回值并且在函数体里面用yield return语句来暂停协程并提交一个唤醒条件。然后使用StartCoroutine来开启协程。
思考协程能做的Update都能做那为什么我们需要协程呢 答使用协程我们可以把一个跨越多帧的操作封装到一个方法内部代码会更清晰。
4. 注意事项
协程是挂在MonoBehaviour上的必须要通过一个MonoBehaviour才能开启协程。MonoBehaviour被Disable的时候协程会继续执行只有MonoBehaviour被销毁的时候协程才会被销毁。协程看起来有点像是轻量级线程但是本质上协程还是运行在主线程上的协程更类似于Update()方法Unity会每一帧去检测协程需不需要被唤醒。一旦你在协程中执行了一个耗时操作很可能会堵塞主线程。这里提供两个解决思路(1) 在耗时算法的循环体中加入yield return null来将算法分到很多帧里面执行(2) 如果耗时操作里面没有使用Unity API那么可以考虑在异步线程中执行耗时操作完成后唤醒主线程中的协程。
二. Unity协程的底层原理
协程分为两部分协程与协程调度器协程仅仅是一个能够中间暂停返回的函数而协程调度是在MonoBehaviour的生命周期中实现的。 准确的说Unity只实现了协程调度部分而协程本身其实就是用了C#原生的”迭代器方法“。
1. 协程本体C#的迭代器函数
许多语言都有迭代器的概念使用迭代器我们可以很轻松的遍历一个容器。 但是C#里面的迭代器要屌一点它可以“遍历函数”。 C#中的迭代器方法其实就是一个协程你可以使用yield来暂停使用MoveNext()来继续执行。 当一个方法的返回值写成了IEnumerator类型他就会自动被解析成迭代器方法后文直接称之为协程你调用此方法的时候不会真的运行而是会返回一个迭代器需要用MoveNext()来真正的运行。看例子
static void Main(string[] args)
{IEnumerator it Test();//仅仅返回一个指向Test的迭代器不会真的执行。Console.ReadKey();it.MoveNext();//执行Test直到遇到第一个yieldSystem.Console.WriteLine(it.Current);//输出1Console.ReadKey();it.MoveNext();//执行Test直到遇到第二个yieldSystem.Console.WriteLine(it.Current);//输出2Console.ReadKey();it.MoveNext();//执行Test直到遇到第三个yieldSystem.Console.WriteLine(it.Current);//输出test3Console.ReadKey();
}
static IEnumerator Test()
{System.Console.WriteLine(第一次执行);yield return 1;System.Console.WriteLine(第二次执行);yield return 2;System.Console.WriteLine(第三次执行);yield return test3;
}
执行Test()不会运行函数体会直接返回一个IEnumerator调用IEnumerator的MoveNext()成员会执行协程直到遇到第一个yield return或者执行完毕。调用IEnumerator的Current成员可以获得yield return后面接的返回值该返回值可以是任何类型的对象。
这里有两个要注意的地方
IEnumerator中的yield return可以返回任意类型的对象事实上它还有泛型版本IEnumeratorT泛型类型的迭代器中只能返回T类型的对象。Unity原生协程使用普通版本的IEnumerator但是有些项目比如倩女幽魂自己造的协程轮子可能会使用泛型版本的IEnumeratorT函数调用的本质是压栈协程的唤醒也一样调用IEnumerator.MoveNext()时会把协程方法体压入当前的函数调用栈中执行运行到yield return后再弹栈。这点和有些语言中的协程不大一样有些语言的协程会维护一个自己的函数调用栈在唤醒的时候会把整个函数调用栈给替换这类协程被称为有栈协程而像C#中这样直接在当前函数调用栈中压入栈帧的协程我们称之为无栈协程。关于有栈协程和无栈协程的概念我们会在后文四. 跳出Unity看协程中继续讨论
Unity中的协程是无栈协程它不会维护整个函数调用栈仅仅是保存一个栈帧。 3. Unity协程的架构
基类YieldInstruction 其它所有协程相关的类都继承自这个类。Unity的协程只允许返回继承自YieldInstruction的对象或者null。如果返回了其他对象则会被当成null处理。
协程类Coroutine 你可以通过yield return一个协程来等待一个协程执行完毕所以Coroutine也会继承自YieldInstruction。 Coroutine仅仅代表一个协程实例不含任何成员方法你可以将Coroutine对象传到MonoBehaviour.StopCoroutine方法中去关闭这个协程。
遗憾的是Unity关于协程的这套都是在C层实现的并且几乎没有暴露出C#接口所以扩展起来会比较麻烦。
三. 扩展Unity的协程
这部分看原文
四. 跳出Unity看协程
1. 进程线程与协程
进程是操作系统资源分配的基本单位 线程是处理器调度与执行的基本单位 这是操作系统书上对进程与线程的抽象描述。具体一点的说进程其实就是程序运行的实例程序本身只是存储在外存上的冷冰冰的二进制流计算机将这些二进制流读进内存并解析成指令和数据然后执行程序便成为了进程。
每一个进程都独立拥有自己的指令和数据所以称为资源分配的基本单位。其中数据又分布在内存的不同区域我们在C语言课程中学习过内存四区的概念一个运行中的进程所占有的内存大体可以分为四个区域栈区、堆区、数据区、代码区。其中代码区存储指令另外三个区存储数据。
线程是处理器调度和执行的基本单位一个线程往往和一个函数调用栈绑定一个进程有多个线程每个线程拥有自己的函数调用栈同时共用进程的堆区数据区代码区。操作系统会不停地在不同线程之间切换来营造出一个并行的效果这个策略称为时间片轮转法。
那么协程在其中又处于什么地位呢 一切用户自己实现的类似于线程的轮子都可以称之为是协程。
C#中的迭代器方法是协程 Unity在迭代器的基础上扩展出来的协程模块是协程 你在操作系统实验中模仿线程自己写出来的线程也是协程 ........
协程有什么样的行为完全由实现协程的程序员来决定线程和进程都是操作系统中写死的这就导致了不同开发框架下的协程差别很大。有的协程有自己的函数调用栈有的协程共用线程的函数调用栈有的协程是单线程上的有的协程可以多线程调度有的协程和线程是一对多的关系有的协程和线程是多对多的关系。
操作系统可以有多个进程 一个进程对应一个或多个线程 线程和协程的对应关系由具体的开发框架决定 2. 不同框架下协程的共同点
虽然不同开发框架下的协程各不一样但是这些协程基本上还是有一些共性的
(1) 协程有yield和resume操作
协程可以通过yield操作挂起通过resume操作恢复。yield一般是协程主动调用resume一般是调度器调用。 大多数协程库都支持这两个操作无非是可能API的名字不一样。 比如C#中resume操作就是MoveNext
(2) 协程调度是非抢占式的
线程调度是抢占式的操作系统会主动中断当前执行中的线程然后把CPU控制权交给别的线程就好像有很多线程去争抢CPU的控制权一样。
协程调度是非抢占式的协程需要主动调用yield来释放CPU控制权协程的运行中间不会被系统中断打断。 可以看看这个扩展
浅谈倩女手游中的资源更新 - 知乎 (zhihu.com)
IFramework/Example-Readme/Bind.md at master · OnClick9927/IFramework (github.com) 参考文章当我们在说协程时我们在说些什么 - Lyon Gu - 博客园 (cnblogs.com)
协程可以将一个方法放到多个帧内执行在很大程度上提高了性能。但协程也是有缺陷的
不支持返回值不支持异常处理不支持泛型不支持锁…