协程(Coroutine)
在 Unity 中,协程(Coroutine) 是一种特殊的函数,可以在运行时暂停和恢复执行,通常用于处理需要分步执行的任务或延迟操作。协程非常适合处理异步操作,例如等待、计时、分帧加载等。
协程的特点
-
暂停和恢复:协程可以在执行过程中暂停(
yield
),并在下一帧或指定条件满足后恢复执行。 -
非阻塞:协程不会阻塞主线程,适合处理耗时操作。
-
基于迭代器:协程的实现基于 C# 的迭代器(
IEnumerator
),使用yield return
控制执行流程。 -
依赖于 MonoBehaviour:协程必须挂载在 MonoBehaviour 脚本中,通过
StartCoroutine
启动。
常用的 yield return
语句
-
yield return null
暂停一帧,下一帧恢复执行。 -
yield return new WaitForSeconds(time)
暂停指定时间(秒),时间到后恢复执行。 -
yield return new WaitForEndOfFrame()
暂停到当前帧结束,下一帧恢复执行。 -
yield return new WaitUntil(condition)
暂停直到指定条件为true
。 -
yield return new WaitWhile(condition)
暂停直到指定条件为false
。 -
yield return StartCoroutine(anotherCoroutine)
暂停并等待另一个协程执行完毕。
如何避免协程中的卡顿
方法 1:将耗时操作分帧执行
-
使用
yield return null
将耗时操作分到多帧中执行。
方法 2:使用异步操作
-
将耗时操作放到异步任务中执行,避免阻塞主线程。
-
IEnumerator LoadDataAsync() { UnityWebRequest request = UnityWebRequest.Get("https://example.com/data"); yield return request.SendWebRequest(); // 异步等待网络请求完成 Debug.Log("数据加载完成: " + request.downloadHandler.text); }
方法 3:使用多线程
-
对于计算密集型任务,可以使用 C# 的多线程(如
Task
或Thread
)来避免阻塞主线程。 -
IEnumerator HeavyCalculationCoroutine() { bool isDone = false; Task.Run(() => { HeavyCalculation(); // 在子线程中执行耗时操作 isDone = true; }); while (!isDone) { yield return null; // 等待子线程完成 } Debug.Log("耗时操作完成"); }
方法 4:使用 Job System 和 Burst Compiler
-
Unity 的 Job System 和 Burst Compiler 可以高效地处理多线程任务,适合性能敏感的场景。
-
[BurstCompile] struct HeavyJob : IJob { public void Execute() { // 耗时操作 } } IEnumerator RunHeavyJob() { var job = new HeavyJob(); JobHandle handle = job.Schedule(); while (!handle.IsCompleted) { yield return null; // 等待任务完成 } handle.Complete(); Debug.Log("耗时操作完成"); }
异步等待
async
关键字
-
用于标记一个方法为异步方法。
-
异步方法的返回类型必须是
Task
、Task<T>
或void
(不推荐使用void
)
await
关键字
-
用于等待一个异步操作完成。
-
await
会暂停当前方法的执行,直到等待的Task
完成。 -
在等待期间,控制权会返回给调用方,避免阻塞线程。
底层原理
-
async
和await
是基于状态机的语法糖。 -
编译器会将
async
方法转换为一个状态机,await
是状态机的暂停点。 -
当
await
的Task
完成后,状态机会从暂停点恢复执行。
避免同步阻塞
-
不要使用
Thread.Sleep
、.Result
或.Wait()
。 -
如果需要延迟,使用
await Task.Delay
。
使用 Task.Run
卸载耗时操作
-
将耗时操作放到后台线程中执行。
-
async Task MyAsyncMethod() { Console.WriteLine("开始耗时操作"); await Task.Run(() => { HeavyCalculation(); // 在后台线程中执行 }); Console.WriteLine("耗时操作完成"); }