Unity协程原理深入剖析

2016年10月17日 11:20 0 点赞 0 评论 更新于 2025-11-21 20:37

记得刚开始实习时,我需要编写网络层结构,用到了协程。当时我完全懵了,根本不清楚Unity协程的执行机制,只知道函数的返回值是IEnumerator类型,在函数中使用yield return,就可以通过StartCoroutine调用。后来我一直稀里糊涂地使用协程,在网上搜索到的大多是示例代码,很少有能帮助我深入理解Unity协程原理的内容。

本文将从Unity的角度分析协程的内部运行原理,而非从C#底层的语法实现进行介绍(后续有需要再做介绍)。文章主要分为三部分:

  1. 线程(Thread)和协程(Coroutine)
  2. Unity中协程的执行原理
  3. IEnumerator & Coroutine

虽然我之前对协程有了一定的了解,但对于Unity如何执行协程仍一无所知。在UnityGems.com上看到两篇讲解协程的文章后,我收获颇丰。当看到Advanced Coroutine后面的Hijack类时,我被其精巧的设计所吸引,于是决定写这篇文章与大家分享。

线程(Thread)和协程(Coroutine)

D.S.Qiu认为使用协程主要有两个作用:一是延时(等待)一段时间后执行代码;二是等待某个操作完成后再执行后续代码。简单来说,就是控制代码在特定的时机执行。

很多初学者会下意识地认为协程是异步执行的,甚至将其视为C#线程的替代品,认为协程是Unity不使用线程的解决方案。

在此,需要明确一点:协程既不是线程,也不是异步执行的。协程和MonoBehaviourUpdate函数一样,都是在主线程(MainThread)中执行的。使用协程时,无需考虑同步和锁的问题。

Unity中协程的执行原理

UnityGems.com对协程的定义如下:

A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.

即协程是一个分部执行的函数,遇到条件(yield return语句)会挂起,直到条件满足才会被唤醒,继续执行后续代码。

Unity在每一帧(Frame)都会处理对象上的协程,主要是在Update方法之后检查协程的条件是否满足。

协程和Update()方法类似,都是Unity每帧会处理的函数(如果存在的话)。如果MonoBehaviour处于激活(active)状态,且yield的条件满足,就会执行协程方法的后续代码。此外,如果在对象的前期调用协程,协程会立即运行到第一个yield return语句处。如果是yield return null,则会在同一帧再次被唤醒。

以下是一段示例代码:

using UnityEngine;
using System.Collections;

public class TestCoroutine : MonoBehaviour {
private bool isStartCall = false; // Make sure Update() and LateUpdate() Log only once
private bool isUpdateCall = false;
private bool isLateUpdateCall = false;

// Use this for initialization
void Start () {
if (!isStartCall) {
Debug.Log("Start Call Begin");
StartCoroutine(StartCoutine());
Debug.Log("Start Call End");
isStartCall = true;
}
}

IEnumerator StartCoutine() {
Debug.Log("This is Start Coroutine Call Before");
yield return new WaitForSeconds(1f);
Debug.Log("This is Start Coroutine Call After");
}

// Update is called once per frame
void Update () {
if (!isUpdateCall) {
Debug.Log("Update Call Begin");
StartCoroutine(UpdateCoutine());
Debug.Log("Update Call End");
isUpdateCall = true;
}
}

IEnumerator UpdateCoutine() {
Debug.Log("This is Update Coroutine Call Before");
yield return new WaitForSeconds(1f);
Debug.Log("This is Update Coroutine Call After");
}

void LateUpdate() {
if (!isLateUpdateCall) {
Debug.Log("LateUpdate Call Begin");
StartCoroutine(LateCoutine());
Debug.Log("LateUpdate Call End");
isLateUpdateCall = true;
}
}

IEnumerator LateCoutine() {
Debug.Log("This is Late Coroutine Call Before");
yield return new WaitForSeconds(1f);
Debug.Log("This is Late Coroutine Call After");
}
}

yield return new WaitForSeconds(1f);改为yield return null;后,日志输出结果与上述情况不同,并未出现之前所说的情况。以下是修改后的代码:

using UnityEngine;
using System.Collections;

public class TestCoroutine : MonoBehaviour {
private bool isStartCall = false; // Make sure Update() and LateUpdate() Log only once
private bool isUpdateCall = false;
private bool isLateUpdateCall = false;

// Use this for initialization
void Start () {
if (!isStartCall) {
Debug.Log("Start Call Begin");
StartCoroutine(StartCoutine());
Debug.Log("Start Call End");
isStartCall = true;
}
}

IEnumerator StartCoutine() {
Debug.Log("This is Start Coroutine Call Before");
yield return null;
Debug.Log("This is Start Coroutine Call After");
}

// Update is called once per frame
void Update () {
if (!isUpdateCall) {
Debug.Log("Update Call Begin");
StartCoroutine(UpdateCoutine());
Debug.Log("Update Call End");
isUpdateCall = true;
}
}

// 此处原代码未完整,推测后续逻辑与前面类似
// ...
}

IEnumerator & Coroutine

(由于原始内容中这部分未详细展开,可根据后续补充内容进一步完善)

通过以上内容,我们对Unity协程的原理有了更深入的理解。协程作为一种强大的工具,能够帮助我们更好地控制代码的执行时机。在实际开发中,合理运用协程可以提高代码的可读性和可维护性。

作者信息

孟子菇凉

孟子菇凉

共发布了 3994 篇文章