Unity 协程深入理解
Unity 协程深入理解学习笔记一、协程的本质协程Coroutine是一种在主线程上分步执行逻辑的机制不是多线程。依赖 C# 的迭代器IEnumerator通过yield return将方法切分成多个“片段”。每一帧执行一个片段执行完就暂停下一帧或条件满足时继续从而实现“分帧处理”避免卡死主线程。二、yield return 的含义yield return是暂停当前协程并返回一个“等待指令”给 Unity 的协程调度器。调度器根据指令类型决定何时从暂停点恢复执行。它不是返回数据而是告诉引擎“我在这儿暂停条件 X 满足时再叫我”。常见 yield 指令指令恢复时机yield return null下一帧的协程调度阶段yield return new WaitForSeconds(t)等待 t 秒后受 timeScale 影响yield return new WaitForSecondsRealtime(t)等待 t 秒不受 timeScale 影响yield return new WaitForEndOfFrame()当前帧渲染结束后yield return new WaitUntil(() condition)每帧检查条件为真时继续yield return new WaitWhile(() condition)每帧检查条件为假时继续yield return StartCoroutine(Another())等待另一个协程完全执行完毕三、协程与 Update 的关系帧循环顺序Unity 每一帧的执行顺序大致如下FixedUpdate → Update → 【协程调度】 → LateUpdate → 渲染textUpdate 和协程不在同一个阶段所有脚本的Update先全部跑完然后引擎才处理协程调度。Update 不会等待协程它们只是流水线上的先后工序协程的等待不会阻塞 Update 的执行。协程里的yield return让出控制权所以即使协程等待 3 秒Update 依然每一帧正常执行。四、StartCoroutine 的细节当场执行第一段StartCoroutine调用时协程方法会立即同步执行到第一个yield return为止然后才返回。剩余部分交给调度器在后续帧处理。voidUpdate(){Debug.Log(1. Update 开始);StartCoroutine(MyCoroutine());Debug.Log(2. Update 结束);}IEnumeratorMyCoroutine(){Debug.Log(3. 协程第一步);// 这一行在 StartCoroutine 调用时当场执行yieldreturnnull;Debug.Log(4. 协程第二步);// 下一帧的协程调度阶段执行}输出第一帧 text1.Update 开始3.协程第一步2.Update 结束 输出第二帧 text1.Update 开始 假设 Update 里有持续调用 StartCoroutine否则没有这一行3.协程第一步 如有新启动2.Update 结束4.协程第二步 上一帧启动的协程恢复[!important]关键理解yieldreturn之前的代码属于 StartCoroutine 调用时的同步执行部分之后的代码才是真正的“协程异步部分”。 五、协程的生命周期与停止 启动 csharpCoroutinecoStartCoroutine(MyCoroutine());// 推荐方式保留句柄// 避免使用 StartCoroutine(MyCoroutine) 字符串方式性能差、易错停止StopCoroutine(co)—— 使用启动时返回的 Coroutine 句柄停止。StopAllCoroutines()—— 停止该 MonoBehaviour 上所有协程。 注意StopCoroutine(MyCoroutine())无效因为每次调用都会生成新的 IEnumerator 实例。 生命周期绑定 协程的生命周期绑定在启动它的 MonoBehaviour 所在的 GameObject 上。 GameObject 被销毁Destroy时该脚本启动的所有协程立即终止。 脚本被 enabledfalse时协程不会自动停止容易踩坑需手动停止。 六、嵌套协程与并行 顺序等待嵌套 csharpIEnumeratorParent(){yieldreturnStartCoroutine(Child1());// 等 Child1 完全结束yieldreturnStartCoroutine(Child2());// 再等 Child2 完全结束Debug.Log(全部完成);}特点一个接一个执行总耗时为两者之和。 并行执行多协程同时跑 直接多次调用 StartCoroutine 即可它们各自独立运行。csharpStartCoroutine(TaskA());StartCoroutine(TaskB());// 两个协程同时进行互不等待若需等待所有并行协程完成可用计数器WaitUntil csharpIEnumeratorWaitAll(){intfinished0;StartCoroutine(RunAndCount(()finished));StartCoroutine(RunAndCount(()finished));yieldreturnnewWaitUntil(()finished2);Debug.Log(全部并行任务结束);}七、多协程的调度机制 所有活跃协程都登记在 Unity 内部的全局调度器列表中不分类。 每一帧的协程调度阶段调度器依次检查每个协程 条件满足如等待时间到了→ 执行一小段直到下一个yieldreturn。 条件未满足 → 跳过不占用 CPU 时间。 所以多个协程看似“一起运行”实则是单线程分时复用每帧轮流推进。 八、跨类协程的注意事项 协程调度是全局统一的不同脚本的协程都在同一个池子里顺序规则完全一致。 StartCoroutine 可以跨类调用otherScript.StartCoroutine(...)但协程的“主人”是那个被调用的 MonoBehaviour随其销毁而终止。 不同脚本的 Update 执行顺序可能不确定可通过 Script Execution Order 设置。 协程中延迟访问外部对象时务必判空因为目标可能在等待期间被销毁。 九、常见陷阱与最佳实践 异常处理协程内未捕获的异常会导致协程静默终止务必加try-catch。 避免字符串启动StartCoroutine(MethodName)有反射开销、参数限制用强类型方式。 GC 优化yieldreturnnewWaitForSeconds 会产生 GC 分配。可缓存复用但注意不能并发复用同一个实例。 csharpWaitForSecondswaitnewWaitForSeconds(1f);yieldreturnwait;// 可顺序复用多次暂停影响WaitForSeconds 受 Time.timeScale 影响游戏暂停时永远不会完成若需忽略暂停用 WaitForSecondsRealtime 或手动计算。 逻辑拆分对于复杂状态机、多重嵌套协程可读性可能下降可考虑async/awaitUniTask或状态机框架。 十、一句话总结 协程就是用yieldreturn把方法切成段由引擎在主线程的 Update 之后分帧执行它不阻塞 Update也不开新线程StartCoroutine 时第一段同步执行后续全靠调度器按条件推进。