当前位置: 首页 > ai >正文

Unity基础学习(七)Mono中的重要内容(3)协同程序的本质

       

目录

一、协程的本质

二、协程本体是迭代器方法的体现

工作原理

访问规则

三、手动实现一个协程调度器


 

        前面我们学习了协程的基本使用方法,你可否有想过他底层的工作原理是什么样的呢?

一、协程的本质

        前面我们实际上已经说了,协同的作用就是将程序分时分步执行。允许将一个任务拆分成多个步骤,在不同帧或特定条件下执行。其本质分为两部分:

(1)协程函数本体
        协程函数是一个用 IEnumerator 定义的迭代器方法,内部通过 yield return 语句分阶段暂停执行。
(2)协程调度器
        Unity 内部实现了协程调度器,负责在合适的时机(如帧更新、等待时间结束等)恢复协程的执行。调度器会根据 yield return 返回的对象决定何时继续执行协程。

其特点是:

非抢占式:协程主动通过 yield 暂停,而不是被系统中断。

单线程:协程在 Unity 主线程运行,无需处理多线程同步问题。

生命周期:与 MonoBehaviour 绑定,对象销毁时协程自动终止。

二、协程本体是迭代器方法的体现

        本标题又该如何理解呢?由于协程本体是定义的迭代器方法,也就是说,他是满足迭代器的语法规则的,而我们实际上yield return 其实是一个语法糖而已,C# 编译器会将迭代器方法编译为一个状态机,每个 yield return 对应一个状态。而它具体是如何工作的呢,主要的关键在IEnumerator接口:包含 MoveNext() 和 Current 属性,用于控制执行流程。

图IEnumerator接口显示

        我们先来仔细看看MoveNext()和Current属性是干什么的:

(1)MoveNext,看名字我们就不难猜出,这个函数的作用是推动程序的分步进行,主要作用就是将迭代器的执行点移动到下一个 yield return 的位置。具有bool类型的返回值:

返回值:返回 bool 类型,表示是否还有后续步骤:

true:迭代尚未结束,存在下一个步骤。
false:迭代已结束,后续调用无效。

工作原理
  1. 首次调用 MoveNext():执行从方法开头到第一个 yield return 之间的代码。

  2. 后续调用:从上一次 yield return 之后继续执行,直到下一个 yield return 或方法结束。

  3. 结束时:最后一次 MoveNext() 返回 false,且 Current 会被重置为 null

例如:

IEnumerator NumberGenerator()
{Debug.Log("Start");yield return 1;     // 第一次 MoveNext() 执行到这里Debug.Log("Step 1");yield return 2;     // 第二次 MoveNext() 执行到这里Debug.Log("End");
}void Test()
{IEnumerator iterator = NumberGenerator();iterator.MoveNext();  // 输出 "Start",Current = 1iterator.MoveNext();  // 输出 "Step 1",Current = 2iterator.MoveNext();  // 输出 "End",返回 false,Current = null
}

(2)Current,获取最近一次yield return返回的对象,只能在 MoveNext() 返回 true 后访问有效值。

访问规则
  1. 在首次调用 MoveNext() 前访问 Current,会得到 null 或未定义值。

  2. 最后一次 MoveNext() 返回 false 后,Current 会被重置为 null

例如:

IEnumerator DataFlow()
{yield return "A";yield return new WaitForSeconds(1);yield return 100;
}void Test()
{IEnumerator ie = DataFlow();while (ie.MoveNext()){Debug.Log(ie.Current); // 输出顺序:// "A" → WaitForSeconds(1) → 100}
}

Unity 协程调度器的伪代码逻辑

// Unity 内部简化逻辑
class CoroutineScheduler 
{List<IEnumerator> activeCoroutines = new List<IEnumerator>();void Update() {foreach (var coroutine in activeCoroutines){if (coroutine.MoveNext()) {object yieldObj = coroutine.Current;// 根据 yieldObj 类型决定何时再次执行:// - null → 下一帧继续// - WaitForSeconds → 计时结束后继续// - WaitForFixedUpdate → 物理帧后继续}else {// 移除已完成的协程}}}
}

小结:

特性MoveNext()Current
类型方法(返回 bool属性(返回 object
主要作用推进迭代器到下一个 yield 位置获取当前 yield return 返回的对象
调用时机必须主动调用以推进迭代器仅在 MoveNext() 返回 true 后有效
典型返回值true(未结束)/false(已结束)yield return 后的对象或 null
Unity 中的角色由协程调度器自动调用用于判断协程暂停条件(如等待时间)

        你可以简化理解迭代器函数
        C#看到的迭代器函数yield return 语法糖,就会把原本是一个的函数 变成几部分,我们就可以通过迭代器 从上到下 遍历这几部分进行执行,就达到了将一个函数中的逻辑分时执行的目的

        而协程调度器就是 利用迭代器函数返回的内容来进行之后的处理
比如 unity中的协程调度器,根据yield return 返回的内容 决定了下一次在何时继续执行迭代器函数中的下一部分

        理论上来说 我们可以自己利用迭代器的特点 自己实现协程调度器来取代unity自带的调度器

三、手动实现一个协程调度器

        首先,有一个对象,这个对象需要具备两个内容,一个是记录下次需要执行的迭代器接口,一个是记录下次执行点条件(这里我们以时间作为条件示例)

public class YieldReturnTime
{//记录 下次还要执行的 迭代器接口public IEnumerator ie;//记录 下次执行的时间点public float time;
}

然后用一个函数记录所有的迭代器接口,方便后续分步执行:

    public void MyStartCoroutine(IEnumerator ie){//来进行 分步走 分时间执行的逻辑//传入一个 迭代器函数返回的结构 那么应该一来就执行它//一来就先执行第一步 执行完了 如果返现 返回的true 证明 后面还有步骤if(ie.MoveNext()){//判断 如果yield return返回的是 数字 是一个int类型 那就证明 是需要等待n秒继续执行if(ie.Current is int){//按思路 应该把 这个迭代器函数 和它下一次执行的时间点 记录下来//然后不停检测 时间 是否到达了 下一次执行的 时间点 然后就继续执行它YieldReturnTime y = new YieldReturnTime();//记录迭代器接口y.ie = ie;//记录时间y.time = Time.time + (int)ie.Current;//把记录的信息 记录到数据容器当中 因为可能有多个协程函数 开启 所以 用一个 list来存储list.Add(y);}}}

记录完所有的可执行接口后,然后根据我们自定义的规则,进行调度

    void Update(){//为了避免在循环的时候 从列表里面移除内容 我们可以倒着遍历for (int i = list.Count - 1; i >= 0; i--){//判断 当前该迭代器函数 是否到了下一次要执行的时间//如果到了 就需要执行下一步了if( list[i].time <= Time.time  ){if(list[i].ie.MoveNext()){//如果是true 那还需要对该迭代器函数 进行处理//如果是 int类型 证明是按秒等待if(list[i].ie.Current is int){list[i].time = Time.time + (int)list[i].ie.Current;}else{//该list 只是存储 处理时间相关 等待逻辑的 迭代器函数的 //如果是别的类型 就不应该 存在这个list中 应该根据类型把它放入别的容器中list.RemoveAt(i);}}else{//后面已经没有可以等待和执行的了 证明已经执行完毕了逻辑list.RemoveAt(i);}}}}

附完整代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class YieldReturnTime
{//记录 下次还要执行的 迭代器接口public IEnumerator ie;//记录 下次执行的时间点public float time;
}public class CoroutineMgr : MonoBehaviour
{/*实现单例*/private static CoroutineMgr instance;public static CoroutineMgr Instance => instance;//申明存储 迭代器函数对象的 容器 用于 一会继续执行private List<YieldReturnTime> list = new List<YieldReturnTime>();// Start is called before the first frame updatevoid Awake(){instance = this;}public void MyStartCoroutine(IEnumerator ie){//来进行 分步走 分时间执行的逻辑//传入一个 迭代器函数返回的结构 那么应该一来就执行它//一来就先执行第一步 执行完了 如果返现 返回的true 证明 后面还有步骤if(ie.MoveNext()){//判断 如果yield return返回的是 数字 是一个int类型 那就证明 是需要等待n秒继续执行if(ie.Current is int){//按思路 应该把 这个迭代器函数 和它下一次执行的时间点 记录下来//然后不停检测 时间 是否到达了 下一次执行的 时间点 然后就继续执行它YieldReturnTime y = new YieldReturnTime();//记录迭代器接口y.ie = ie;//记录时间y.time = Time.time + (int)ie.Current;//把记录的信息 记录到数据容器当中 因为可能有多个协程函数 开启 所以 用一个 list来存储list.Add(y);}}}// Update is called once per framevoid Update(){//为了避免在循环的时候 从列表里面移除内容 我们可以倒着遍历for (int i = list.Count - 1; i >= 0; i--){//判断 当前该迭代器函数 是否到了下一次要执行的时间//如果到了 就需要执行下一步了if( list[i].time <= Time.time  ){if(list[i].ie.MoveNext()){//如果是true 那还需要对该迭代器函数 进行处理//如果是 int类型 证明是按秒等待if(list[i].ie.Current is int){list[i].time = Time.time + (int)list[i].ie.Current;}else{//该list 只是存储 处理时间相关 等待逻辑的 迭代器函数的 //如果是别的类型 就不应该 存在这个list中 应该根据类型把它放入别的容器中list.RemoveAt(i);}}else{//后面已经没有可以等待和执行的了 证明已经执行完毕了逻辑list.RemoveAt(i);}}}}
}

http://www.xdnf.cn/news/8344.html

相关文章:

  • PyQt5安装,在Pycharm上配置以及使用教程
  • 设计模式-备忘录模式
  • 【安装指南】Canal 环境的安装与使用
  • 手写一个简单的线程池
  • SQL实战之索引失效案例详解
  • Python在自动驾驶中的多传感器融合——让智能汽车“看得更清楚”
  • “Agent上车”浪潮来临,谁在引领新一轮的AI座舱交互变革?
  • JMeter 教程:监控性能指标 - 第三方插件安装(PerfMon)
  • SQL SERVER中实现类似LEAST函数的功能,返回多列数据中的最小值
  • 6个月Python学习计划 Day 2
  • python 实现一个完整的基于Python的多视角三维重建系统,包含特征提取与匹配、相机位姿估计、三维重建、优化和可视化等功能
  • Javase易混点专项复习02_static关键字
  • Day125 | 灵神 | 二叉树 | 二叉树中的第K大层和
  • 003-类和对象(二)
  • Ubuntu Linux系统的基本命令详情
  • 李宏毅《机器学习2025》笔记 —— 更新中
  • 使用 uv 工具从 pyproject.toml 和 uv.lock 快速安装 Python 依赖
  • 10G SFP+ 双纤光模块选购避坑指南:从SFP-10G-LRM到SFP-10G-ZR的兼容性与应用
  • C语言中的文件I/O
  • 用算法实现 用统计的方式实现 用自然语言处理的方法实现 用大模型实现 专利精益化统计分析
  • Attu下载 Mac版与Win版
  • 电磁兼容(EMC)仿真(精编版)
  • pytorch LSTM 结构详解
  • PR-2014《The MinMax K-Means clustering algorithm》
  • HTML5的新语义化标签
  • 腾讯地图WebServiceAPI提供基于HTTPS/HTTP协议的数据接口
  • JAVA:Kafka 存储接口详解与实践样例
  • 练习小项目7:天气状态切换器②
  • 机器学习中的维度、过拟合、降维
  • 从制造到智造:猎板PCB的技术实践与产业价值重构