Unity 协程异步与计时器速查

这篇按“等待、延迟、倒计时、异步加载”来查:用协程还是 async,怎么停止,暂停时计时器是否继续。


快速索引

我想做什么 推荐写法
等一帧 yield return null
等几秒,受暂停影响 WaitForSeconds
等几秒,不受暂停影响 WaitForSecondsRealtime
等条件成立 WaitUntil
停止指定协程 保存 Coroutine 句柄
简单延迟调用 Invoke
重复调用 InvokeRepeating
文件、网络、资源异步 async/await
取消 async CancellationTokenSource
冷却倒计时 Time.deltaTime
真实时间倒计时 Time.unscaledDeltaTime

0. 协程基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private Coroutine fadeCoroutine;

public void PlayFade()
{
if (fadeCoroutine != null)
{
StopCoroutine(fadeCoroutine); // 重复启动前先停旧的
}

fadeCoroutine = StartCoroutine(FadeRoutine());
}

private IEnumerator FadeRoutine()
{
yield return null; // 等一帧
yield return new WaitForSeconds(1f); // 等 1 秒,受 Time.timeScale 影响
fadeCoroutine = null;
}

1. 常用 yield

1
2
3
4
5
6
7
yield return null;                           // 下一帧继续
yield return new WaitForSeconds(1f); // 等 1 秒,暂停时也暂停
yield return new WaitForSecondsRealtime(1f); // 等真实 1 秒,不受暂停影响
yield return new WaitUntil(() => isReady); // 条件为 true 后继续
yield return new WaitWhile(() => isLoading); // 条件为 false 后继续
yield return new WaitForEndOfFrame(); // 当前帧渲染结束后
yield return new WaitForFixedUpdate(); // 下一个物理帧

2. 停止协程

停止指定协程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private Coroutine routine;

private void StartWork()
{
routine = StartCoroutine(WorkRoutine());
}

private void StopWork()
{
if (routine != null)
{
StopCoroutine(routine); // 停止指定协程
routine = null;
}
}

禁用时清理:

1
2
3
4
private void OnDisable()
{
StopWork(); // 界面关闭或对象禁用时停止
}

停止全部:

1
StopAllCoroutines();                         // 停止当前 MonoBehaviour 上全部协程

3. Invoke / InvokeRepeating

1
2
Invoke(nameof(Hide), 1.5f);                   // 1.5 秒后调用 Hide
CancelInvoke(nameof(Hide)); // 取消指定延迟调用
1
2
InvokeRepeating(nameof(Refresh), 0f, 1f);     // 立刻开始,每 1 秒调用一次
CancelInvoke(nameof(Refresh)); // 停止重复调用

适合:

1
2
3
简单延迟隐藏
简单重复刷新
临时测试逻辑

慎用:

1
2
3
复杂流程
需要参数的方法
重构方法名频繁变化

4. async / await

1
2
3
4
5
private async Task LoadDataAsync()
{
await Task.Delay(1000); // 等 1 秒,不依赖 Unity 帧
Debug.Log("Loaded");
}

按钮调用:

1
2
3
4
5
6
7
8
9
10
11
private async void OnClickLoad()
{
try
{
await LoadDataAsync();
}
catch (Exception ex)
{
Debug.LogError(ex); // async void 里要自己兜异常
}
}

Unity 对象检查:

1
2
3
4
5
6
await LoadDataAsync();

if (this == null || gameObject == null) // await 回来后对象可能已销毁
{
return;
}

5. CancellationToken

1
2
3
4
5
6
7
8
9
10
11
12
13
private CancellationTokenSource cts;

private void OnEnable()
{
cts = new CancellationTokenSource();
}

private void OnDisable()
{
cts?.Cancel(); // 禁用时取消任务
cts?.Dispose();
cts = null;
}

带取消的任务:

1
2
3
4
private async Task LoadAsync(CancellationToken token)
{
await Task.Delay(1000, token); // token 取消时抛 OperationCanceledException
}

调用:

1
2
3
4
5
6
7
8
try
{
await LoadAsync(cts.Token);
}
catch (OperationCanceledException)
{
Debug.Log("任务已取消");
}

6. Update 计时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private float cooldown;

private void Update()
{
if (cooldown > 0f)
{
cooldown -= Time.deltaTime; // 受暂停影响
}
}

public bool CanUseSkill => cooldown <= 0f;

public void UseSkill()
{
if (!CanUseSkill)
{
return;
}

cooldown = 3f; // 开始 3 秒冷却
}

不受暂停影响:

1
timer -= Time.unscaledDeltaTime;              // 不受 Time.timeScale 影响

7. 倒计时 UI

1
2
3
4
5
6
7
8
9
10
11
12
private float remainTime = 10f;

private void Update()
{
if (remainTime <= 0f)
{
return;
}

remainTime -= Time.deltaTime;
timeText.text = Mathf.CeilToInt(remainTime).ToString(); // 显示整数秒
}

减少 UI 刷新:

1
2
3
4
5
6
7
8
9
10
11
12
private int lastSecond = -1;

private void Update()
{
int second = Mathf.CeilToInt(remainTime);

if (second != lastSecond)
{
timeText.text = second.ToString(); // 数字变化才刷新文本
lastSecond = second;
}
}

8. 选择速查

1
2
3
4
5
协程              # Unity 帧流程、等待秒、分帧动画
Invoke # 简单延迟
async/await # 文件、网络、任务组合
TimerManager # 大量统一计时器
DOTween Sequence # UI 动画和串行动画流程

9. 常见坑速查

1
2
3
4
5
6
7
协程重复启动不停止旧的               # 逻辑执行多次
WaitForSeconds 受 Time.timeScale 影响 # 暂停时不走
对象销毁后 await 继续访问 UI # MissingReferenceException
async void 不 try/catch # 异常难查
OnDisable 不取消任务 # 切场景后旧逻辑继续跑
Invoke 用字符串方法名 # 重构后可能失效
对象池回收不 StopCoroutine # 取出后残留旧协程

系列导航