在 Unity 中,协程(Coroutine)是一种特殊的函数,它可以暂停执行并在之后的某个时间点继续执行,这为开发者处理异步操作提供了便利。Unity 中的协程是一种基于迭代器模式实现的异步编程机制,它通过 yield return
语句暂停和继续执行,由协程管理器进行管理,为开发者提供了一种简单、高效的方式来处理异步操作。
目录
1 协程的执行流程
2 原理
3 协程与线程的区别
4 协程的使用案例
1 协程的执行流程
协程本质上是一个返回类型为 IEnumerator
的方法,它可以包含一个或多个 yield return
语句。yield return
语句用于暂停协程的执行,并将控制权交还给调用者。当满足特定条件时,协程会从暂停的位置继续执行。
- 启动协程:在 Unity 中,可以使用
StartCoroutine
方法来启动一个协程。当调用StartCoroutine
时,Unity 会将协程加入到一个协程管理器中,协程开始执行,直到遇到第一个yield return
语句。 - 暂停执行:当协程遇到
yield return
语句时,它会暂停执行,并将控制权交还给调用者。yield return
后面可以跟不同的值,这些值决定了协程何时继续执行。例如:yield return null
:表示在下一帧继续执行协程。yield return new WaitForSeconds(1)
:表示等待 1 秒后继续执行协程。yield return new WaitForFixedUpdate()
:表示在下一次固定更新(FixedUpdate)之后继续执行协程。
- 继续执行:当满足
yield return
语句指定的条件时,协程会从暂停的位置继续执行,直到遇到下一个yield return
语句或协程结束。 - 协程结束:当协程执行完所有代码,或者遇到
yield break
语句时,协程结束。
2 原理
- 迭代器模式:在 C# 中,协程是基于迭代器模式实现的。
IEnumerator
接口定义了一个迭代器,它包含MoveNext
方法和Current
属性。当调用StartCoroutine
时,Unity 会创建一个迭代器对象,并调用其MoveNext
方法来执行协程的代码。当遇到yield return
语句时,MoveNext
方法返回true
,并将yield return
后面的值赋给Current
属性。当协程结束时,MoveNext
方法返回false
。
(迭代器模式(Iterator Pattern)是一种行为设计模式,它提供了一种方法来顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
抽象迭代器(Iterator):定义了访问和遍历元素的接口,通常包含 MoveNext
方法用于移动到下一个元素,Current
属性用于获取当前元素,Reset
方法用于将迭代器重置到初始位置。
具体迭代器(Concrete Iterator):实现了抽象迭代器接口,负责具体的元素遍历逻辑,它会维护一个指向当前元素的指针。
抽象聚合(Aggregate):定义了创建迭代器对象的接口,通常包含一个 CreateIterator
方法。
具体聚合(Concrete Aggregate):实现了抽象聚合接口,负责创建具体的迭代器对象。
)
- 协程管理器:Unity 内部有一个协程管理器,负责管理所有正在运行的协程。在每一帧的更新循环中,协程管理器会检查每个协程的状态,根据
yield return
语句指定的条件决定是否继续执行协程。(协程管理器的主要职责就是跟踪协程的状态,根据yield return
语句所设定的条件来决定何时继续执行协程。)
3 协程与线程的区别
- 线程:线程是操作系统调度的最小单位,多个线程可以并行执行,它们在不同的 CPU 核心上同时运行,因此可以充分利用多核处理器的性能。但是线程的创建和销毁开销较大,并且需要处理线程同步和锁的问题,容易出现死锁和数据竞争等问题。
(隐藏提问点:Unity中多线程执行哪些代码会报错?
Unity 的很多核心对象和组件是与主线程紧密绑定的,直接在多线程中对它们进行操作会报错。(直接操作 Unity 的主线程对象,例如:游戏对象(GameObject)相关操作,组件(Component)相关操作)
渲染相关的操作和 API 依赖于主线程和特定的渲染上下文,在多线程中访问会导致错误。( 访问 Unity 的渲染相关 API)调用 Unity 的生命周期方法,访问 Unity 的输入系统)
- 协程:协程是在单线程中实现的,它通过
yield return
语句暂停和继续执行,不会像线程那样进行上下文切换,因此开销较小。协程主要用于处理异步操作,例如网络请求、动画播放等,避免阻塞主线程。
4 协程的使用案例
private void LoadAsyncCore<T>(string path, ResourcesLoadCallback<T> callBack = null, bool isTypeParam = false) where T : Object{string resName = (isTypeParam? path + "_type_" : path + "_") + typeof(T).Name;ResInfo<T> info = GetOrCreateResInfo<T>(resName);info.CallBack += callBack;info.Coroutines = MonoMgr.Instance.StartCoroutine(LoadAsyncCoroutine(path, info, isTypeParam));}private IEnumerator LoadAsyncCoroutine<T>(string path, ResInfo<T> info, bool isTypeParam = false) where T : Object{ResourceRequest res;yield return res=isTypeParam?Resources.LoadAsync(path, typeof(T)):Resources.LoadAsync<T>(path);info.Asset=isTypeParam?(T)res.asset:res.asset as T;if (info.RefCount == 0)UnLoadAsset<T>(path, false, null, false);else{info.CallBack?.Invoke(info.Asset);info.CallBack = null;info.Coroutines = null;}}