协程
协程是 Unity 内置的异步机制,通过 yield 暂停执行,实现任务在多帧中分段执行。与普通函数不同,协程可在执行过程中挂起和恢复,呈现"并发"效果,但本质上仍运行于主线程。若在协程中进行耗时操作,仍会导致主线程阻塞。因此,协程更适合处理异步事件的调度,而非需要多线程计算的任务。
StartCoroutine()
是启动器,不是执行体yield return StartCoroutine()
是等待子协程完成的声明- Unity协程调度器在背后管理执行流程
优势
- 使用简洁:在需要处理耗时的异步操作(如等待 HTTP 传输、资源加载、文件 I/O)时,官方建议优先考虑协程。
- Unity 生命周期集成:协程由
MonoBehaviour
管理,随脚本或对象生命周期自动中止。比如当GameObject
被停用或销毁时,其上运行的协程也会停止。 - 无需线程切换:协程运行在 Unity 主线程,不必担心线程安全,因为代码始终在主线程执行。
劣势
- 依赖 MonoBehaviour
- 无法直接获取返回值:协程返回类型为
IEnumerator
,无法直接提供结果。通常需通过回调、全局状态或等待协程结束来获取数据,不如Task<T>
直接。 - 错误处理复杂:在协程内不能直接使用
try/catch
捕获包含yield
的代码块中的异常,这使得异常处理更加困难。未处理的异常可能导致游戏逻辑问题被掩盖。 - 性能与GC开销:尽管单个协程开销小,但大量协程会增加垃圾回收压力并可能影响帧率(如装箱迭代器、
WaitForSeconds
等对象),尤其是在每帧更新后统一调度时。每次 yield 都产生新对象。
使用场景
需要按照顺序执行、需要暂停、运行比单个帧花费时间更长的操作。
- 将对象移动到某个位置。
- 为对象提供要执行的任务列表。
- 淡入淡出视觉或音频。
- 等待资源加载:如
Resources.LoadAsync
/SceneManager.LoadSceneAsync
。 - 网络请求封装:先
UnityWebRequest.SendWebRequest()
,然后yield return request
等待请求完成,再检查结果。
yield return
Yield 表示该方法是一个迭代器 (IEnumerator),它将在多个帧上执行,而 return 与常规函数一样,在该点终止执行并将控制权传递回调用方法。
不同之处在于,对于协程,Unity 知道从上次中断的地方继续该方法。yield return 后面的内容将指定 Unity 在继续之前将等待多长时间。
yield return null
- 等待下一帧yield return new WaitForSeconds(秒数)
- 等待一段时间
对于重复延迟,可以创建一个对象优化性能。
WaitForSeconds delay = new WaitForSeconds(1); // 创建对象Coroutine coroutine;void Start(){StartCoroutine("MyCoroutine");}IEnumerator MyCoroutine(){int i = 100;while (i > 0){// Do something 100 timesi--;yield return delay; // 使用对象}// All Done!}
yield return new WaitForSecondsRealtime(秒数)
- 实时等待秒数yield return new WaitUntil(条件)/WaitWhile(条件)
- 等待委托- yield return new WaitUntil(() => condition) → 等待条件成立
- yield return new WaitWhile(() => condition) → 等待条件不再成立
1. 用外部函数
yield return new WaitUntil(IsEmpty);
bool void IsEmpty()
{...//返回true或false
}2. 用lambda表达式
IEnumerator CheckFuel()
{yield return new WaitWhile(() => fuel > 0);...
}
yield return new WaitForEndOfFrame()
- 等待帧结束
会等到 Unity 渲染完每个 Camera 和 UI 元素,然后再实际显示帧。这方面的典型用途是截取屏幕截图。yield return StartCoroutine()
- 等待另一个协程
void Start(){// 启动协程StartCoroutine(MyCoroutine());}IEnumerator MyCoroutine(){// 启动等待第二个协程yield return StartCoroutine(MyOtherCoroutine());}IEnumerator MyOtherCoroutine(){...yield return new WaitForSeconds(1);}
结束协程
yield break
- 在协程完成前结束协程StopCoroutine
- 在协程外部停止协程
bool stopCoroutine;
Coroutine runningCoroutine;void Start(){runningCoroutine = StartCoroutine(MyCoroutine());}void Update(){if (stopCoroutine == true){StopCoroutine(runningCoroutine);stopCoroutine = false;}}IEnumerator MyCoroutine(){// Coroutine stuff...}
StopAllCoroutines()
- 停止MonoBehaviour上的所有协程
需要从启动它们的行为中调用 。
其他
- 推荐网址介绍
Async
async 关键字用于定义异步方法,配合 await 使用,可以在不阻塞Unity主线程的情况下执行耗时任务(如网络请求、文件I/O),从而使主线程保持流畅。
优势
- 和同步代码结构类似:有线性的代码执行流程,并且支持直接返回值。
- 错误处理方便:可以用try-catch捕捉错误处理。
- 高效的任务组合:内置并行处理机制
var task1 = LoadPlayerDataAsync();
var task2 = LoadLevelDataAsync();await Task.WhenAll(task1, task2); // 并行等待
- 性能优化:状态机更轻量(特别是配合 UniTask);更少的 GC 压力。
劣势
- Unity 集成问题:需要处理线程上下文
- 时间控制复杂:默认不受 Time.timeScale 影响,需要手动实现游戏时间等待
- 生命周期管理复杂:需要手动取消任务
使用场景
- 网络请求和 I/O 操作
- 复杂异步逻辑组合
- 后台线程和 CPU 密集型任务
- 需要精细错误处理的场景
关键字
- Task:表示一个异步操作,用于执行异步任务并返回结果。
- async 关键字:用于修饰方法,表明该方法是异步的,可以使用 await 进行异步调用。
- await 关键字:用于等待异步操作完成,避免阻塞主线程。异步方法在碰到await表达式之前都是使用同步的方式执行。
对比
异步加载资源对比:
IEnumerator LoadAssetsCoroutine()
{// 顺序加载ResourceRequest request1 = Resources.LoadAsync<Texture>("Texture1");yield return request1;ResourceRequest request2 = Resources.LoadAsync<Texture>("Texture2");yield return request2;// 并行加载(有限支持)ResourceRequest[] requests = new ResourceRequest[3];for (int i = 0; i < 3; i++) {requests[i] = Resources.LoadAsync<GameObject>($"Prefab_{i}");}// 手动等待所有完成while (requests.Any(r => !r.isDone)) {yield return null;}Debug.Log("All assets loaded");
}
async Task LoadAssetsAsync()
{// 顺序加载var texture1 = await Resources.LoadAsync<Texture>("Texture1").AsTask();var texture2 = await Resources.LoadAsync<Texture>("Texture2").AsTask();// 并行加载var loadTasks = new List<Task>();for (int i = 0; i < 3; i++) {loadTasks.Add(Resources.LoadAsync<GameObject>($"Prefab_{i}").AsTask());}await Task.WhenAll(loadTasks);Debug.Log("All assets loaded");
}
代码结构与可读性对比:
async/await 允许按照同步代码的思维方式编写异步逻辑,可以直接返回值,像同步方法一样调用其他异步方法。
IEnumerator LoadGameData()
{// 1. 加载玩家数据UnityWebRequest playerReq = UnityWebRequest.Get(playerDataURL);yield return playerReq.SendWebRequest();PlayerData player = JsonUtility.FromJson<PlayerData>(playerReq.downloadHandler.text);// 2. 加载关卡数据UnityWebRequest levelReq = UnityWebRequest.Get(levelDataURL);yield return levelReq.SendWebRequest();LevelData level = JsonUtility.FromJson<LevelData>(levelReq.downloadHandler.text);// 3. 初始化游戏InitializeGame(player, level);
}
async Task LoadGameDataAsync()
{// 1. 加载玩家数据PlayerData player = await LoadPlayerDataAsync();// 2. 加载关卡数据LevelData level = await LoadLevelDataAsync();// 3. 初始化游戏InitializeGame(player, level);
}async Task<PlayerData> LoadPlayerDataAsync()
{UnityWebRequest req = UnityWebRequest.Get(playerDataURL);await req.SendWebRequest();return JsonUtility.FromJson<PlayerData>(req.downloadHandler.text);
}