在Unity中处理延迟和异步操作时,常用以下三种方式:Invoke、Coroutine 和 async/await。以下是它们的详细介绍、示例代码及使用场景对比。
一、Invoke(基于字符串的延迟调用)
特点:
-
通过方法名的字符串调用,简单但类型不安全。
-
支持单次延迟(
Invoke
)和重复调用(InvokeRepeating
)。 -
依赖
MonoBehaviour
,停止时需调用CancelInvoke
。
示例代码:
using UnityEngine;
public class InvokeExample : MonoBehaviour
{
void Start()
{
// 单次延迟调用:2秒后执行SpawnEnemy
Invoke("SpawnEnemy", 2f);
// 重复调用:3秒后开始,每隔1秒执行一次Shoot
InvokeRepeating("Shoot", 3f, 1f);
}
void SpawnEnemy() {
Debug.Log("生成敌人!");
}
void Shoot() {
Debug.Log("射击!");
}
void OnDisable() {
// 停止所有Invoke调用
CancelInvoke();
}
}
缺点:
-
字符串方法名易出错(如重命名方法后不会报错)。
-
无法传递参数。
二、Coroutine(协程)
特点:
-
基于
IEnumerator
和yield
,灵活控制执行流程。 -
可暂停和恢复,适合复杂异步逻辑(如等待资源加载、动画完成)。
-
通过
StartCoroutine
启动,StopCoroutine
停止。
示例代码:
using UnityEngine;
using System.Collections;
public class CoroutineExample : MonoBehaviour
{
void Start()
{
StartCoroutine(SpawnEnemyRoutine());
}
IEnumerator SpawnEnemyRoutine()
{
Debug.Log("协程开始");
yield return new WaitForSeconds(2f); // 等待2秒
SpawnEnemy();
yield return new WaitForSeconds(1f); // 再等待1秒
Debug.Log("继续执行");
// 循环生成敌人,每3秒一次
while (true)
{
SpawnEnemy();
yield return new WaitForSeconds(3f);
}
}
void SpawnEnemy() {
Debug.Log("生成敌人!");
}
void OnDisable() {
StopAllCoroutines(); // 停止所有协程
}
}
进阶用法:
-
yield return new WaitUntil(() => condition);
:等待条件满足。 -
yield return StartCoroutine(OtherRoutine());
:嵌套协程。
三、async/await(C#原生异步)
特点:
-
语法简洁,类似同步代码的异步操作。
-
需Unity 2017+,推荐使用
UniTask
库优化性能。 -
注意线程安全:Unity API需在主线程调用。
示例代码:
using UnityEngine;
using System.Threading.Tasks;
public class AsyncExample : MonoBehaviour
{
async void Start()
{
Debug.Log("异步开始");
await Task.Delay(2000); // 等待2秒(单位:毫秒)
SpawnEnemy();
await LoadResourceAsync(); // 等待资源加载
Debug.Log("资源加载完成");
}
async Task LoadResourceAsync()
{
// 模拟异步加载(如AssetBundle)
await Task.Delay(1000);
Debug.Log("资源加载完毕");
}
void SpawnEnemy() {
Debug.Log("生成敌人!");
}
}
线程安全操作:
async void Start()
{
// 在后台线程执行耗时计算
int result = await Task.Run(() => HeavyCalculation());
// 返回主线程更新UI
await Task.Yield(); // 或使用 UniTask的SwitchToMainThread()
Debug.Log("计算结果:" + result);
}
int HeavyCalculation() {
// 模拟耗时计算
return 42;
}
四、对比与使用场景
方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Invoke | 简单易用 | 类型不安全、无法传参 | 简单延迟(如3秒后销毁对象) |
Coroutine | 灵活、支持复杂流程控制 | 代码稍显冗长 | 动画序列、分阶段任务 |
async/await | 现代语法、易处理复杂异步逻辑 | 需注意线程安全(Unity API) | 网络请求、文件IO操作 |
最佳实践:
-
简单延迟:优先用
Invoke
或Coroutine
。 -
复杂异步(如网络请求):优先用
async/await
。 -
循环任务:
Coroutine
的while
循环更直观。