Unity 协程探究

article/2025/11/1 5:26:55

一、官方手册中的描述

1、Manual/Coroutines

函数在调用时, “从调用到返回” 都发生在一帧之内,想要处理 “随时间推移进行的事务”, 相比Update,使用协程来执行此类任务会更方便。

协程在创建时,通常是一个 “返回值类型 为 IEnumerator”、“函数体中包含 yield return 语句 ” 的函数。

yiled return 可以暂停协程的执行,并在恰当时候恢复。具体在何时恢复,由 yield 的返回值决定。

启动协程,必须使用 MonoBehaviour 的 StartCoroutine 方法。

停止协程,可以使用 MonoBehaviour 的 StopCoroutine 方法 或 StopAllCoroutine 方法。

注意:以下情况也可能使协程停止
   1)、销毁启动协程的组件(GameObject.Destory(component);) ==> 协程停止
   2)、禁用启动协程的组件(component.enabled = false;)==> 协程不停止
   3)、销毁启动协程的组件所在的物体(GameObject.Destory(gameobject);) ==> 协程停止
   4)、隐藏启动协程的组件所在的物体(gameobject.SetActive(false);) ==> 协程停止

2、MonoBehaviour.StartCoroutine

StartCoroutine 方法总是立刻返回一个 Coroutine 对象(同步返回)。

无法保证协同程序按其启动顺序结束,即使他们在同一帧中完成也是如此(异步无序完成)。

可以在一个协程中启动另一个协程(支持协程嵌套)。

二、Unity中的 yield 语句类型

1、yield break;    //打断协程运行

2、yield return null;    //挂起协程,并从下一帧继续

3、yield return + “任意数字”;    //挂起协程,并从下一帧继续

4、yield return + “bool值”;    //挂起协程,并从下一帧继续

5、yield return + “任意字符串”;    //挂起协程,并从下一帧继续

6、yield return + “普通Object”;    //挂起协程,并从下一帧继续

7、yield return + “任意实现了 IEnumerator 接口的对象”。重要!(可嵌套)

    Unity 中,常见的、直接或间接实现了 IEnumerator 接口的类有:
        ------------------------------------------------------------------------------------------------
        CustomYieldInstruction (abstarct)   ——|>   IEnumerator (interface)
       
------------------------------------------------------------------------------------------------
        WaitUnitil (sealed)   ——|>   CustomYieldInstruction
        WaitWhile (sealed)
  ——|>   CustomYieldInstruction
        WaitForSecondsRealtime (非sealed,但未发现子类) 
——|>   CustomYieldInstruction
        WWW (非sealed,但未发现子类) 
——|>   CustomYieldInstruction
       
------------------------------------------------------------------------------------------------
        随着Unity更新或在一些可选的Package中,可能有更多。。。
        ------------------------------------------------------------------------------------------------

8、yield return + “任意继承了 YieldInstruction 类 ([UsedByNativeCode],源码C#层中无具体实现) 的对象”。重要!(可嵌套)

    Unity 中,常见的、直接或间接继承了 YieldInstruction 类的类有:
        ------------------------------------------------------------------------------------------------
        WaitForSeconds (sealed)   ——|>   YieldInstruction
        Coroutine (sealed)  ——|>   YieldInstruction (Coroutine 是 StartCoroutine方法的返回值,意味着协程中可嵌套协程)
        WaitForEndOfFrame (sealed) ——|>   YieldInstruction
        WaitForFixedUpdate (sealed)  ——|>   YieldInstruction
        AsyncOperation ——|>   YieldInstruction
       
------------------------------------------------------------------------------------------------
         AssetBundleCreateRequest (非sealed,但未发现子类) ——|>   AsyncOperation
        
AssetBundleRecompressOperation (非sealed,但未发现子类) ——|>   AsyncOperation
        
AssetBundleRequest (非sealed,但未发现子类) ——|>   AsyncOperation
        
ResourceRequest (非sealed,但未发现子类) ——|>   AsyncOperation
        
UnityEngine.Networking.UnityWebRequestAsyncOperation (非sealed,但未发现子类) ——|>   AsyncOperation
        
UnityEngine.iOS.OnDemandResourcesRequest (sealed) ——|>   AsyncOperation
        ------------------------------------------------------------------------------------------------
        随着Unity更新或在一些可选的Package中,可能有更多。。。
        ------------------------------------------------------------------------------------------------

***测试验证 第2、3、4、5、6条 如下:

using System.Collections;
using UnityEngine;public class Test : MonoBehaviour
{void Start(){StartCoroutine(Func1());}IEnumerator Func1(){Debug.Log("Time.frameCount: " + Time.frameCount);yield return null;Debug.Log("Time.frameCount: " + Time.frameCount);yield return 0;Debug.Log("Time.frameCount: " + Time.frameCount);yield return 1;Debug.Log("Time.frameCount: " + Time.frameCount);yield return 99; //其他整数Debug.Log("Time.frameCount: " + Time.frameCount);yield return 0.5f; //浮点数值Debug.Log("Time.frameCount: " + Time.frameCount);yield return false; //bool值Debug.Log("Time.frameCount: " + Time.frameCount);yield return "Hi NRatel!";  //字符串Debug.Log("Time.frameCount: " + Time.frameCount);yield return new Object();  //任意对象Debug.Log("Time.frameCount: " + Time.frameCount);}
}

***测试验证 第7条 如下:

using System.Collections;
using UnityEngine;public class Test : MonoBehaviour
{void Start(){StartCoroutine(Func1());}IEnumerator Func1(){Debug.Log("Func1");yield return Func2();}IEnumerator Func2(){Debug.Log("Func2");yield return Func3();}IEnumerator Func3(){Debug.Log("Func3");yield return null;}
}

三、Unity协程实现原理

1、C# 的迭代器。

现在已经知道:协程肯定与IEnumerator有关,因为启动协程时需要一个 IEnumerator 对象。
而 IEnumerator 是C#实现的 迭代器模式 中的 枚举器(用于迭代的游标)。

迭代器相关接口定义如下:

namespace System.Collections
{//可枚举(可迭代)对象接口public interface IEnumerable{IEnumerator GetEnumerator();}//迭代游标接口public interface IEnumerator{object Current { get; }bool MoveNext();void Reset();}
}

参考 MSDN C#文档中对于 IEnumerator、IEnumerable、迭代器 的描述。

利用 IEnumerator 对象,可以对与之关联的 IEnumerable 集合 进行迭代:

    1)、通过 IEnumerator 的 Current 方法,可以获取集合中位于枚举数当前位置的元素。

    2)、通过 IEnumerator 的 MoveNext 方法,可以将枚举数推进到集合的下一个元素。如果 MoveNext 越过集合的末尾, 则枚举器将定位在集合中最后一个元素之后, 同时 MoveNext 返回 false。 当枚举器位于此位置时, 对 MoveNext 的后续调用也将返回 false 。如果最后一次调用 MoveNext 时返回 false,则 Current 未定义(结果为null)。

    3)、通过 IEnumerator 的 Reset 方法,可以将“迭代游标” 设置为其初始位置,该位置位于集合中第一个元素之前。

2、C# 的 yield 关键字。

C#编译器在生成IL代码时,会将一个返回值类型为 IEnumerator 的方法(其中包含一系列的 yield return 语句),构建为一个实现了 IEnumerator 接口的对象。

注意,yield 是C#的关键字,而非Unity定义!IEnumerator 对象 也可以直接用于迭代,并非只能被Unity的 StartCoroutine 使用!

using System.Collections;
using UnityEngine;public class Test : MonoBehaviour
{void Start(){IEnumerator e = Func();while (e.MoveNext()){Debug.Log(e.Current);}}IEnumerator Func(){yield return 1;yield return "Hi NRatel!";yield return 3;}
}

对上边C#代码生成的Dll进行反编译,查看IL代码:

3、Unity 的协程。

  Unity 协程是在逐帧迭代的,这点可以从 Unity 脚本生命周期 中看出。

可以大胆猜测一下,实现出自己的协程(功能相似,能够说明逐帧迭代的原理,不是Unity源码):

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Test : MonoBehaviour
{private Dictionary<IEnumerator, IEnumerator> recoverDict;   //key:当前迭代器 value:子迭代器完成后需要恢复的父迭代器private IEnumerator enumerator;private void Start(){//Unity自身的协程//StartCoroutine(Func1());//自己实现的协程StarMyCoroutine(Func1());}private void StarMyCoroutine(IEnumerator e){recoverDict = new Dictionary<IEnumerator, IEnumerator>();enumerator = e;recoverDict.Add(enumerator, null);  //完成后不需要恢复任何迭代器}private void LateUpdate(){if (enumerator != null){DoEnumerate(enumerator);}}private void DoEnumerate(IEnumerator e){object current;if (e.MoveNext()){current = e.Current;}else{//迭代结束IEnumerator recoverE = recoverDict[e];if (recoverE != null){recoverDict.Remove(e);}//恢复至父迭代器, 若没有则会至为nullenumerator = recoverE;return;}//null,什么也不做,下一帧继续if (current == null) { return; }Type type = current.GetType();//基础类型,什么也不做,下一帧继续if (current is System.Int32) { return; }if (current is System.Boolean) { return; }if (current is System.String) { return; }//IEnumerator 类型, 等待内部嵌套的IEnumerator迭代完成再继续if (current is IEnumerator){//切换至子迭代器enumerator = current as IEnumerator;recoverDict.Add(enumerator, e);return;}//YieldInstruction 类型, 猜测也是类似IEnumerator的实现if (current is YieldInstruction){//省略实现return;}}IEnumerator Func1(){Debug.Log("Time.frameCount: " + Time.frameCount);yield return null;Debug.Log("Time.frameCount: " + Time.frameCount);yield return "Hi NRatel!";Debug.Log("Time.frameCount: " + Time.frameCount);yield return 3;Debug.Log("Time.frameCount: " + Time.frameCount);yield return new WaitUntil(() =>{return Time.frameCount == 20;});Debug.Log("Time.frameCount: " + Time.frameCount);yield return Func2();Debug.Log("Time.frameCount: " + Time.frameCount);}IEnumerator Func2(){Debug.Log("XXXXXXXXX");yield return null;Debug.Log("YYYYYYYYY");yield return Func3();   //嵌套 IEnumerator}IEnumerator Func3(){Debug.Log("AAAAAAAA");yield return null;Debug.Log("BBBBBBBB");yield return null;}
}

对比结果,基本可以达成协程作用,包括 IEnumerator 嵌套。
但是 Time.frameCount 的结果不同,想来实现细节必然是有差别的。

四、部分Unity源码分析

1、CustomYieldInstruction 类

可以继承该类,并实现自己的、需要异步等待的类

原理:
    当协程中 yield return “一个CustomYieldInstruction的子类”; 其实就相当于在原来的 迭代器A 中,插入了一个 新的迭代器B。
    当迭代程序进入 B ,如果 keepWaiting 为 true,MoveNext() 就总是返回 true。
    上面已经说过,迭代器在迭代时,MoveNext() 返回false 才标志着迭代完成!
    那么,B 就总是完不成,直到 keepWaiting 变为 false。
    这样 A 运行至 B处就 处于了 等待B完成的状态,相当于A挂起了。

 猜测 YieldInstruction 也是类似的实现。

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_Licenseusing System.Collections;namespace UnityEngine
{public abstract class CustomYieldInstruction : IEnumerator{public abstract bool keepWaiting{get;}public object Current{get{return null;}}public bool   MoveNext() { return keepWaiting; }   public void   Reset() {}}
}

2、WaitUntil 类

    语义为 “等待...直到满足...”
    继承自 CustomYieldInstruction,需要等待时让 m_Predicate 返回 false (keepWating为true)。

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_Licenseusing System;namespace UnityEngine
{public sealed class WaitUntil : CustomYieldInstruction{Func<bool> m_Predicate;public override bool keepWaiting { get { return !m_Predicate(); } }public WaitUntil(Func<bool> predicate) { m_Predicate = predicate; }}
}

3、WaitWhile 类

    语义为 “等待...如果满足...”
    继承自 CustomYieldInstruction,需要等待时让 m_Predicate 返回 true (keepWating为true)。
    与 WaitUntil 的实现恰好相反。

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_Licenseusing System;namespace UnityEngine
{public sealed class WaitWhile : CustomYieldInstruction{Func<bool> m_Predicate;public override bool keepWaiting { get { return m_Predicate(); } }public WaitWhile(Func<bool> predicate) { m_Predicate = predicate; }}
}


http://chatgpt.dhexx.cn/article/7gWGVuep.shtml

相关文章

Unity 协程底层原理解析

1、协程 unity是单线程设计的游戏引擎&#xff0c;unity实际上有多条渲染线程&#xff0c;但对于unity调用我们编写的游戏脚本&#xff0c;都是放在一个主线程当中进行调度的。因此对于我们写的游戏脚本unity是单线程的。 协程不是进程或者线程&#xff0c;它的执行过程更类似…

Unity协程那些事儿

Unity协程那些事儿 1、什么是协程&#xff1f;2、协程的使用3、关于yield4、关于IEnumerator/IEnumerable5、从IEnumerator/IEnumerable到yield6、Unity协程机制的实现原理7、源码分析8、总结 1、什么是协程&#xff1f; 用过Unity的应该都知道协程&#xff0c;今天就给大家来…

Unity⭐️当Unity协程遇到while

文章目录 Unity协程是一个老生常谈的知识点了&#xff0c;但今天博主却差点被一篇文章忽悠了 那就是这句话&#xff1a; 为什么说这句话不对呢 那就是当遇到while时&#xff0c;我们再来检验这句话的正确性&#xff1a; 按上面那句话&#xff0c;应该每次yield return后&…

深入探讨Unity协程及其使用

深入探讨Unity协程及其使用 协程 协程在Unity中是个很重要的东东&#xff0c;相信很多人都使用过&#xff0c;它能够非常方便的进行异步等待操作&#xff0c;可以说&#xff0c;用好协程&#xff0c;可以使你的代码更加优雅&#xff0c;然而&#xff0c;如果用不好&#xff0…

Unity 协程、模拟协程

理解协程 IEnumeratoryield 强大的迭代器 IEnumerator 就是一个函数容器 里面保存了一个一个的函数 IEnumator会依次执行每个函数 而每个函数都有一个返回值 保存在IEnumator.Currect里面 看下面这个例子 IEnuermator start_Coroutine(){Debug.Log("HellWord")…

unity 协程特点

协程 一. Unity中使用协程1. 什么是协程2. 如何使用3. 协程的应用场景创建补间动画打字机效果异步加载资源 4. 注意事项 二. Unity协程的底层原理1. 协程本体&#xff1a;C#的迭代器函数2. 协程调度&#xff1a;MonoBehaviour生命周期中实现3. Unity协程的架构 三. 扩展Unity的…

Unity协程深入及实现

Unity协程深入 文章目录 Unity协程深入Unity协程简介Unity协程的用法协程的原理协程的缺陷自己实现协程参考 Unity协程简介 Unity中的协程能够允许我们将一个任务分散到多个帧中。注意&#xff1a;协程&#xff08;协同执行&#xff09;和多线程有本质区别。 协程不是线程&…

unity 协程

首先声明&#xff1a;协程不是线程&#xff0c;协程在主线程中运行&#xff0c;而线程是单独开辟线程 以下是u3d关于协程的调用机制的解释&#xff1a; “在Unity3D中&#xff0c;使用MonoBehaviour.StartCoroutine方法即可开启一个协同程序&#xff0c;也就是说该方法必须在…

【Unity】Unity协程(Coroutine)的原理与应用

文章目录 前言一、什么是协程二、应用场景1.异步加载资源2.将一个复杂程序分帧执行3.定时器 三、协程的使用注意事项 四、Unity协程的底层原理1. 协程本体&#xff1a;C#的迭代器函数2. 协程调度&#xff1a;MonoBehaviour生命周期中实现 五、参考资料 前言 本文是作者在学习U…

Unity-协程详解

1. 简介 unity的**协程&#xff08;Coroutine&#xff09;**只是在c#的基础上做了一层封装&#xff0c;其实yield是C#的关键字。 unity协程是一个能够暂停协程执行&#xff0c;暂停后立即返回主函数&#xff0c;执行主函数剩余的部分&#xff0c;直到中断指令完成后&#xff…

Unity 协程(Coroutine)原理与用法详解

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

linux的用户管理

1.linux的用户管理 linux的用户管理和组管理&#xff0c; 每个用户都必须要有一个且仅有一个初始组&#xff0c;可以有多个附加组&#xff0c;使用useradd命令创建用户时&#xff0c;如果没有指定初始组&#xff0c;系统默认会创建一个于其同名的组。 用户和组信息保存在4个文…

Linux用户管理练习

Linux下用户分为3类&#xff1a;超级用户&#xff08;root&#xff09;、系统用户、普通用户。 超级用户的用户名为root&#xff0c;它具有一切操作权力&#xff0c;因此为安全起见&#xff0c;建议不要轻易的在root账户下面对文件进行操作。在Linux操作系统的字符界面&#xf…

Linux用户管理工具

Linux用户管理工具 1. 用户 1.1创建用户 useradd -m username该命令为用户创建相应的帐号和用户目录/home/username&#xff1b; passwd username该命令为用户设置密码 1.2 删除用户 userdel -r username不带选项使用 userdel&#xff0c;只会删除用户。用户的家目录将…

Linux上的用户管理

Linux是一个多用户多任务的系统&#xff0c;任何人想要访问系统资源&#xff0c;必须通过登录账号来访问系统资源 添加用户 useradd&#xff1a;添加用户的命令&#xff08;root才能添加用户&#xff09; 用法&#xff1a;useradd 用户名 例&#xff1a;useradd water 就创建…

实现Linux用户管理

1.添加用户组&#xff1a;groupadd 用户组名称 2.创建用户 &#xff08;一&#xff09;仅创建用户&#xff1a;useradd 用户名 &#xff08;二&#xff09;指定用户所属的用户组&#xff1a; useradd -g 用户组 3.设置用户密码 &#xff08;一&#xff09;passwd 普通用户名 …

linux用户管理及操作指令

1、首先了解下linux是一个多用户多任务的操作系统。任何一个需要使用资源的用户都需要从linux系统中分配一个用户角色&#xff0c;比如&#xff1a;root、user、、然后使用对应账号进入系统。一个root用户下面能创建多个用户&#xff0c;每个用户下面对应一个目录 2、添加用户…

浅谈Linux用户管理

Linux用户管理 准备工作&#xff1a; 在管理用户时&#xff0c;执行命令后是无法看到效果的&#xff0c;最开始我们可以通过系统监视命令来对用户信息进行监控&#xff0c;使操作步骤可视。 watch -n 1 tail -n3 /etc/passwd;ls -l /home/ 注释&#xff1a; 部分含义watch …

Linux用户管理(Centos7)

用户管理 用户命令 添加登录用户&#xff1a; 例&#xff1a;添加一个名为harry的用户&#xff0c;并使用bash作为登录的shell [rootaws ~]# useradd harry [rootaws ~]# tail -1 /etc/passwd harry:x:1002:1002::/home/harry:/bin/bashharry:x:1001:1001::/home/harry:/bin/…