unity,通俗解释什么是协程

Posted 忽然602

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了unity,通俗解释什么是协程相关的知识,希望对你有一定的参考价值。

文章目录


一、 介绍

在Unity中,协程是一种轻量级的线程,可以在同一线程中执行多个任务,实现异步等待和延迟执行等效果。协程的执行是基于迭代器的,可以通过yield语句来实现暂停和恢复执行,从而更加灵活地控制协程的执行过程。协程可以在不创建新线程的情况下实现异步等待和延迟执行,避免了线程切换和同步等问题,从而提高了程序的性能和效率。

在Unity中,协程通常用于处理一些异步任务,比如等待一段时间、播放动画、加载资源等。协程的执行过程可以通过yield语句来控制,比如yield return new WaitForSeconds(5)可以让协程等待5秒钟后再继续执行。此外,协程还可以通过yield return null来让协程在下一帧继续执行,从而实现延迟执行的效果。协程可以在MonoBehaviour组件中使用,也可以在普通的C#类中使用。

需要注意的是,协程并不是多线程,协程的执行是在主线程中进行的,因此协程中的操作不应该阻塞主线程的执行。如果协程需要执行一些耗时的操作,可以将其放到后台线程中执行,然后通过yield return null等方式来控制协程的执行过程。


二、 举例子

协程不是顺序!
只有一个线程。
生活中的协程例子:

在做饭的时候,我们需要根据食材的不同,进行不同的烹饪方式,比如先煮汤再放菜,等菜熟了再加调料,这就需要协程来控制。我们可以使用协程来暂停烹饪的过程,等待食材煮熟或煮熟到一定程度后再继续加入其他调料,以达到更好的口感。

在学习的时候,我们需要进行不同的任务,比如先阅读书籍,再做练习题,最后进行总结,这也需要协程来控制。我们可以使用协程来暂停学习的过程,等待某些条件满足或者一定时间后再继续进行下一步的任务。

在旅游的时候,我们需要根据不同的景点和时间,安排不同的行程,比如先去景点A,再去景点B,最后在晚上回到酒店,这也需要协程来控制。我们可以使用协程来暂停旅游的过程,等待到达某个景点或者一定时间后再继续前往下一个景点。

总之,协程是一种在生活中经常出现的流程控制方式,可以帮助我们更加高效地完成任务。


三、 和多线程的区别

协程和多线程都是在编程中用来实现并发执行的技术,但它们有一些区别:

线程是由操作系统调度的,而协程则是由程序员手动调度的。程序员可以在协程中自行决定何时暂停、恢复和切换协程的执行,而线程的调度则是由操作系统来完成的。

线程是在操作系统层面上实现的,并且每个线程都有自己的堆栈,需要占用一定的系统资源。而协程则是在程序层面上实现的,多个协程可以共享同一个线程,并且不需要额外的系统资源。

多线程的切换需要进行上下文切换,需要保存和恢复线程的状态,而协程的切换则不需要进行上下文切换,只需要保存和恢复协程的状态,所以协程的切换速度更快。

多线程的并发执行可能会涉及到线程间的竞态条件和锁,需要使用同步机制来保证线程安全,而协程的并发执行则可以通过协程间的消息传递来实现,不需要使用复杂的同步机制。

总之,协程和多线程都可以实现并发执行,但它们的实现方式和特点都有所不同,需要根据具体的应用场景来选择适合的技术。


四、 常用函数

StartCoroutine:用于启动一个协程,接受一个IEnumerator类型的参数,表示要执行的协程方法。

StopCoroutine:用于停止指定的协程,接受一个Coroutine类型的参数,表示要停止的协程。

WaitForSeconds:用于暂停协程的执行,接受一个float类型的参数,表示暂停的时间,单位是秒。

WaitForEndOfFrame:用于暂停协程的执行,直到当前帧结束后再继续执行。

WaitForFixedUpdate:用于暂停协程的执行,直到下一次FixedUpdate时再继续执行。

WWW:用于异步加载网络资源,接受一个string类型的参数,表示要加载的资源路径。

yield return null:用于暂停协程的执行,直到下一帧再继续执行。

yield return new WaitForSeconds(float time):用于暂停协程的执行,直到指定的时间后再继续执行。

yield return StartCoroutine(IEnumerator routine):用于在协程中启动另一个协程。


五、 脚本

做游戏,我要用协程等待5秒播放动画

using UnityEngine;
using System.Collections;

public class WaitAndPlayAnimation : MonoBehaviour 

    public Animator anim;

    void Start () 
        StartCoroutine (WaitAndPlay ());
    

    IEnumerator WaitAndPlay () 
        yield return new WaitForSeconds (5f);
        anim.SetTrigger ("playAnimation");
    


Unity 从各个点理解协程

什么是协同程序?什么是协程?
Unity 协程是一个能够暂停协程执行,暂停后立即返回主函数
执行主函数剩余的部分,直到中断指令完成后,从中断指令的下一行继续执行协程剩余的函数
函数体全部执行完成,协程结束
由于中断指令的出现,使得可以将一个函数分割到多个帧里去执行
而在性能上,协程相比于一般函数并没有更多的开销

协程的好处
让原来要使用异步 + 回调方式写的非人类代码, 可以用看似同步的方式写出来
能够分步做一个比较耗时的事情,如果需要大量的计算,将计算放到一个随时间进行的协程来处理,能分散计算压力

协程的坏处
协程本质是迭代器,且是基于 Unity 生命周期的,大量开启协程会引起 GC
如果同时激活的协程较多,就可能会出现多个高开销的协程挤在同一帧执行导致的卡帧

协程书写时的性能优化
常见的问题是直接 new 一个中断指令,带来不必要的 GC 负担,可以复用一个全局的中断指令对象,优化掉开销
在 Yielders.cs 这个文件里,已经集中地创建了上面这些类型的静态对象
这个链接分析了一下https://blog.csdn.net/liujunjie612/article/details/70623943

协程是在什么地方执行?
协程不是线程,不是异步执行
协程和 Monobehaviour 的 Update 函数一样也是在主线程中执行
Unity 在每一帧都会处理对象上的协程,也就是说,协程跟 Update 一样是 Unity 每帧都会去处理的函数
经过测试,协程至少是每帧的 LateUpdate 后运行的

参照 Unity 的生命周期图

协程怎么结束?
方法一: StopCoroutine(string methodName);
方法二: StopAllCoroutines 暂停的是当前脚本下的所有协程
方法三: gameObject.active = false 可以停止该对象上全部协程的执行,即使再次激活,也不能继续执行
    但注意 MonoBehaviour enabled = false 不能停止协程
    对比 Update 却是可以在 MonoBehaviour enabled = false 就中止

**原因:**由于协程是在 StartCoroutine 时被注册到的 GameObject 上
   他的生命期受限于 GameObject 的生命期,因此受 GameObject 是否 Active 的影响
**结论:**协程虽然是在 MonoBehvaviour 启动的(StartCoroutine)

但是协程函数的地位完全是跟 MonoBehaviour 是一个层次的,不受 MonoBehaviour 的状态影响

协程结束的标志是什么?
如果最后一个 yield return 的 IEnumerator 已经迭代到最后一个时,MoveNext 就会返回 false

这时,Unity 就会将这个 IEnumerator 从 cortoutines list 中移除

只有当这个对象的 MoveNext() 返回 false 时,即该 IEnumertator 的 Current 已经迭代到最后一个元素了,才会执行 yield return 后面的语句

中断函数类型
null 在下一帧所有的 Update() 函数调用过之后执行
WaitForSeconds() 等待指定秒数,在该帧(延迟过后的那一帧)所有 Update() 函数调用完后执行
即等待给定时间周期, 受 Time.timeScale 影响,当 Time.timeScale = 0f 时,yield return new WaitForSecond(x) 将不会满足
WaitForFixedUpdate 等待一个固定帧,即等待物理周期循环结束后执行
WaitForEndOfFrame 等待帧结束,即等待渲染周期循环结束后执行
StartCoroutine 等待一个新协程暂停
WWW 等待一个加载完成,等待 www 的网络请求完成后,isDone=true 后执行

协程的执行顺序
开始协程 ->
执行协程 ->
遇到中断指令中断协程 ->
返回上层函数继续执行上层函数的下一行代码 ->
中断指令结束后,继续执行中断指令之后的代码 ->
协程结束

协程可以嵌套协程吗?
可以,yield return StartCoroutine 就是,执行顺序是:
子协程中断后,会返回父协程,父协程暂停,返回父协程的上级函数
决定父协程结束的标志是子协程是否结束,当子协程结束后返回父协程执行其后的代码才算结束
同一时刻同一脚本实例中能有多少个运行的协程?
在一个 MonoBehaviour 提供的主线程里只能有一个处于运行状态的协程
因为协程不是线程,不是并行的
同一时刻、一个脚本实例中可以有多个暂停的协程,但只有一个运行着的协程

协程和线程的区别?
线程是利用多核达到真正的并行计算,缺点是会有大量的锁、切换、等待的问题
而协程是非抢占式,需要用户自己释放使用权来切换到其他协程
因此同一时间其实只有一个协程拥有运行权, 相当于单线程的能力
协程是 C# 线程的替代品,是 Unity 不使用线程的解决方案

使用协程不用考虑同步和锁的问题
多个协程可以同时运行,它们会根据各自的启动顺序来更新

其他注意点
1、IEnumerator 类型的方法不能带 ref 或者 out 型的参数,但可以带被传递的引用
2、在函数 Update 和 FixedUpdate 中不能使用 yield 语句,否则会报错, 但是可以启动协程
3、在一个协程中,StartCoroutine()和 yield return StartCoroutine()是不一样的
  前者仅仅是开始一个新的 Coroutine,这个新的 Coroutine 和现有 Coroutine 并行执行
  后者是返回一个新的 Coroutine,是一个中断指令,当这个新的 Coroutine 执行完毕后,才继承执行现有 Coroutine

以上是关于unity,通俗解释什么是协程的主要内容,如果未能解决你的问题,请参考以下文章

Unity 从各个点理解协程

Unity 从各个点理解协程

Unity 从各个点理解协程

unity游戏物体隐藏了协程会如何

unity游戏开发看什么书?

通俗易懂,unity和c#是什么关系