优势
协程
- 协程是与
MonoBehaviour
绑定的,如果没有继承自MonoBehaviour
就无法使用
- 协程会产生很多GC,每次调用
yield return
都会创建一个 IEnumerator
对象。如果使用yield return 0
或yield return new WaitForSeconds(2f)
还会出现装箱的操作
- Unity 每帧会检查所有正在运行的协程并执行,频繁的上下文切换可能会影响性能
- 无法通过
try/catch
捕获异常
- 协程是跟随UnityObject生命周期的,GameObject被销毁后协程也会停止
- 停止协程比较麻烦
- 不支持返回值
原生 async/await
- 每个
Task
都是一个类对象,它在创建时会分配堆内存
- 无法支持WebGL
- 在部分情况下无法捕获异常
- 不支持返回值
UniTask
- UniTask是针对Unity的异步编程,支持WebGL
- 主要通过结构体来管理异步,避免内存分配和GC压力
- 轻松捕获异常
- 便利的停止异步任务
- 支持返回值
UniTaskTracker
1 2 3 4 5 6 7 8 9 10 11
| void start() { ExampleUniTask().Forget(); } async UniTask ExampleUniTask() { for (int i = 0; i < 100; i++) { await UniTask.Delay(50); } }
|


如果没有按照正确方式使用AsyncUniTask
不会消失,例如将上面的代码改成:
1 2 3 4
| void start() { ExampleUniTask(); }
|
正确使用的两种方式:
- 使用
await
修饰:后面的代码会等待执行完毕后再执行
- 使用
.Forget()
:不会等待,直接执行后面的代码

Status
- Succeeded:执行完毕
- Canceled:被
CancellationTokenSource
取消
WhenAll
与WhenAny
他们两个最主要的区别就是什么时候结束等待,开始执行后面的代码
1 2 3 4 5 6
| await UniTask.WhenAll( ExampleUniTask(bar1, 50), ExampleUniTask(bar2, 100), ExampleUniTask(bar3, 100) ); Debug.Log("All tasks completed");
|
也可以用来控制取消任务
1 2 3 4 5 6 7
| await UniTask.WhenAny( ExampleUniTask(bar1, cts.Token), ExampleAsync(bar2, cts.Token).AsUniTask(), ExampleCoroutine(bar3).WithCancellation(cts.Token) ); cts.Cancel(); Debug.Log("All tasks completed");
|

使进程跟随GameObject生命周期
就像前面优势说的,UniTask即使在物体被摧毁后也能执行,那么如何控制UniTask跟随GameObject生命周期呢
方法一:
1 2 3 4 5 6
| private async void Start() { var token = this.GetCancellationTokenOnDestroy();
await ExampleUniTask(bar1, token); }
|
方法二:
1 2 3 4 5 6 7 8 9 10
| private CancellationTokenSource _cts = new CancellationTokenSource();
private async void Start() { await ExampleUniTask(bar1, _cts.Token); } private void OnDestroy() { _cts?.Cancel(); }
|
返回值
无返回值
如果不在意返回的内容可以使用UniTaskVoid
当然,这样就不能使用await
修饰了,更不能加入到WhenAll
或WhenAny
了
1 2 3 4 5 6 7 8 9
| async UniTaskVoid ExampleUniTask(CancellationToken token) { for (int i = 0; i < 100; i++) { token.ThrowIfCancellationRequested(); await UniTask.Delay(50); Debug.Log(i); } }
|
有返回值
如果需要返回值,必须使用await
修饰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void Start() { var (a, b, c) = await UniTask.WhenAll( DelayedValueAsync(1, 5), DelayedValueAsync(2, 10), DelayedValueAsync(3, 20) ); Debug.Log($"{a}, {b}, {c}"); }
async UniTask<int> DelayedValueAsync(int value, int delayFrames) { await UniTask.DelayFrame(delayFrames); await UniTask.Yield(PlayerLoopTiming.FixedUpdate); return value * 2; }
|
其他
线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| private async void Start() { Debug.Log($"主线程ID: {Thread.CurrentThread.ManagedThreadId}"); using (var cts = new CancellationTokenSource()) { ExampleUniTask(bar1, cts.Token).Forget(); ExampleAsync(bar2, cts.Token); StartCoroutine(ExampleCoroutine(bar3)); } }
async UniTask ExampleUniTask(HealthScrollbar healthScrollbar, CancellationToken token) { Debug.Log($"ExampleUniTask线程ID: {Thread.CurrentThread.ManagedThreadId}"); for (int i = 0; i < 100; i++) { token.ThrowIfCancellationRequested(); await UniTask.Delay(50, cancellationToken: token); healthScrollbar.Number++; } }
async Task ExampleAsync(HealthScrollbar healthScrollbar, CancellationToken token) { Debug.Log($"ExampleAsync线程ID: {Thread.CurrentThread.ManagedThreadId}"); for (int i = 0; i < 100; i++) { token.ThrowIfCancellationRequested(); await Task.Delay(50, token); healthScrollbar.Number++; } }
IEnumerator ExampleCoroutine(HealthScrollbar healthScrollbar, CancellationToken token) { Debug.Log($"ExampleCoroutine线程ID: {System.Threading.Thread.CurrentThread.ManagedThreadId}"); for (int i = 0; i < 100; i++) { yield return new WaitForSeconds(0.05f); healthScrollbar.Number++; } }
|

Unity是单线程引擎,Unity 的大部分 API 都是必须在主线程(也称为 Unity 线程)上运行的,包括游戏对象的更新、UI 操作和物理引擎等。因此,无论是 Coroutine
、UniTask
还是 Task
,如果它们没有显式地切换到其他线程,默认都会在主线程上运行。
1 2 3 4 5 6 7 8 9
| private async void Start() { Debug.Log($"主线程ID: {Thread.CurrentThread.ManagedThreadId}"); int result = await Task.Run(() => { Debug.Log($"ExpensiveCalculation线程ID: {Thread.CurrentThread.ManagedThreadId}"); return ExpensiveCalculation(); }); }
|

Task.Run
是专门设计在后台线程上执行任务,而不是主线程。主线程在执行 await 之后,会暂时释放控制权,等待任务完成,而任务则在后台线程上执行。
将其他方法转换成UniTask方法
1 2 3 4 5 6 7 8
| using (var cts = new CancellationTokenSource()) { await UniTask.WhenAll( ExampleUniTask(bar1, cts.Token), ExampleAsync(bar2, cts.Token).AsUniTask(), ExampleCoroutine(bar3).WithCancellation(cts.Token) ); }
|
将UniTask转换成协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| IEnumerator ExampleCoroutine(HealthScrollbar healthScrollbar) { yield return ExampleUniTask(_cts.Token).ToCoroutine(); }
async UniTask ExampleUniTask(CancellationToken token) { for (int i = 0; i < 100; i++) { token.ThrowIfCancellationRequested(); await UniTask.Delay(50); Debug.Log(i); } }
|