Unity3D 进阶教程 - 04 UniTask 与高级异步编程

概述

入门系列中我们用协程(Coroutine)处理延时和异步操作。协程的局限性很明显:不能返回值、无法处理异常、不能在协程外等待它完成、每帧有 MoveNext 的装箱开销。

UniTask 是 Unity 专用的高性能异步框架,它实现了 C# 的 Task-like 模式,零 GC 分配,完全兼容 async/await 语法,并且深度集成 Unity 的生命周期。

1. 协程的痛点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 协程:不能返回值
private IEnumerator LoadData()
{
var request = UnityWebRequest.Get("https://api.example.com/data");
yield return request.SendWebRequest();
// 结果只能通过回调传出
}

// 协程:不能 try-catch
private IEnumerator DoSomething()
{
yield return SomeOperation();
// 这里抛异常会直接中断协程,无法捕获
}

// 协程:调用方无法等待完成
void SomeMethod()
{
StartCoroutine(MyCoroutine());
// 无法知道 MyCoroutine 何时结束
}

2. UniTask 基础

2.1 安装

通过 Package Manager 添加:

1
https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask

引入命名空间:

1
using Cysharp.Threading.Tasks;

2.2 基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 异步方法:返回 UniTask
private async UniTask LoadAndDisplayAsync()
{
var data = await FetchDataAsync();
Debug.Log("加载完成: {data}");
}

// 异步方法:返回带值的 UniTask<T>
private async UniTask<string> FetchDataAsync()
{
await UniTask.Delay(1000);
return "Hello UniTask";
}

2.3 替代常用协程 API

协程 UniTask
yield return new WaitForSeconds(1) await UniTask.Delay(1000)
yield return null await UniTask.Yield()
yield return new WaitForEndOfFrame() await UniTask.WaitForEndOfFrame()
yield return new WaitForFixedUpdate() await UniTask.WaitForFixedUpdate()
yield return new WaitUntil(() => flag) await UniTask.WaitUntil(() => flag)
yield return new WWW(url) await UnityWebRequest.Get(url).SendWebRequest()

3. 取消操作(CancellationToken)

协程无法在启动后被外部取消(只能 StopCoroutine 或销毁物体)。UniTask 通过 CancellationToken 实现标准的取消流程:

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
private CancellationTokenSource _cts;

private void Start()
{
_cts = new CancellationTokenSource();
StartLongOperation(_cts.Token).Forget();
}

private void OnDestroy()
{
_cts?.Cancel();
_cts?.Dispose();
}

private async UniTask StartLongOperation(CancellationToken token)
{
try
{
for (int i = 0; i < 100; i++)
{
token.ThrowIfCancellationRequested();

await UniTask.Delay(100, cancellationToken: token);
Debug.Log("进度: {i}%");
}
}
catch (OperationCanceledException)
{
Debug.Log("操作被取消");
}
}

3.1 自动绑定生命周期

1
2
3
4
private async UniTaskVoid Start()
{
await SomeOperation(this.GetCancellationTokenOnDestroy());
}

UniTaskVoid 类似 async void,但不会吞异常(异常会触发 UniTaskScheduler 的全局异常处理)。


4. 并行与组合

4.1 等待多个任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 并行加载,等待所有完成
private async UniTask LoadAllAsync()
{
var task1 = LoadTextureAsync("texture1");
var task2 = LoadTextureAsync("texture2");
var task3 = LoadAudioAsync("bgm");

await UniTask.WhenAll(task1, task2, task3);
Debug.Log("所有资源加载完成");
}

// 竞速:谁先完成就用谁
private async UniTask<string> FetchFastestAsync()
{
var server1 = FetchFromServer("https://server1.example.com");
var server2 = FetchFromServer("https://server2.example.com");

var result = await UniTask.WhenAny(server1, server2);
return result.result;
}

4.2 超时控制

1
2
3
4
5
6
7
8
9
10
11
12
13
private async UniTask<string> FetchWithTimeoutAsync(string url)
{
try
{
var task = UnityWebRequest.Get(url).SendWebRequest().ToUniTask();
return await task.Timeout(TimeSpan.FromSeconds(3));
}
catch (TimeoutException)
{
Debug.LogWarning("请求超时,使用缓存数据");
return GetCachedData();
}
}

5. 与 Unity 组件深度集成

5.1 等待异步操作

1
2
3
4
5
6
await SceneManager.LoadSceneAsync("GameScene").ToUniTask();

await Resources.LoadAsync<GameObject>("Prefabs/Enemy").ToUniTask();

var handle = Addressables.LoadAssetAsync<GameObject>("Enemy");
await handle.ToUniTask();

5.2 等待事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private async UniTask WaitForPlayerClickAsync()
{
Debug.Log("等待玩家点击...");
await UniTask.WaitUntil(() => Input.GetMouseButtonDown(0));
Debug.Log("玩家点击了");
}

private async UniTask PlayAnimationAsync(Animator animator, string stateName)
{
animator.Play(stateName);
await UniTask.WaitUntil(() =>
{
var state = animator.GetCurrentAnimatorStateInfo(0);
return state.IsName(stateName) && state.normalizedTime >= 1f;
});
}

5.3 渐变补间

用 UniTask 实现简单的补间动画,无需 DOTween:

1
2
3
4
5
6
7
8
9
10
11
12
13
private async UniTask FadeCanvasGroup(CanvasGroup group, float targetAlpha, float duration)
{
float startAlpha = group.alpha;
float elapsed = 0f;

while (elapsed < duration)
{
elapsed += Time.deltaTime;
group.alpha = Mathf.Lerp(startAlpha, targetAlpha, elapsed / duration);
await UniTask.Yield();
}
group.alpha = targetAlpha;
}

6. 异步工厂与对象池

UniTask 和对象池的结合特别适合需要延迟创建的场景:

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
public class AsyncBulletPool : MonoBehaviour
{
[SerializeField] private GameObject _bulletPrefab;
private readonly Queue<Bullet> _pool = new();
private CancellationToken _ct;

private void Awake()
{
_ct = this.GetCancellationTokenOnDestroy();
}

public async UniTask<Bullet> SpawnAsync(Vector3 position, Quaternion rotation)
{
Bullet bullet;

if (_pool.Count > 0)
{
bullet = _pool.Dequeue();
bullet.gameObject.SetActive(true);
}
else
{
var handle = Addressables.InstantiateAsync(_bulletPrefab.name, position, rotation);
var go = await handle.ToUniTask(cancellationToken: _ct);
bullet = go.GetComponent<Bullet>();
}

bullet.transform.position = position;
bullet.transform.rotation = rotation;
await PlaySpawnEffectAsync(bullet.transform.position);
return bullet;
}
}

7. 错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private async UniTask SafeLoadAsync()
{
try
{
var data = await FetchDataAsync();
ProcessData(data);
}
catch (UnityWebRequestException ex)
{
Debug.LogError("网络请求失败: {ex.ResponseCode} - {ex.Message}");
await ShowErrorPanelAsync("网络错误,请重试");
}
catch (OperationCanceledException)
{
Debug.Log("操作被用户取消");
}
catch (Exception ex)
{
Debug.LogError("未知错误: {ex}");
await ShowErrorPanelAsync("发生未知错误");
}
}

8. 性能对比

场景 协程 UniTask
空循环 10000 次 ~15ms ~0.3ms
每帧 GC 分配 是(enumerator 状态机) 否(struct 状态机)
返回值 不支持 支持 UniTask
异常处理 无法 catch 完整 try-catch
取消支持 需额外实现 CancellationToken
跨场景持久 停止 可控
Unity 生命周期集成 原生 通过 GetCancellationTokenOnDestroy

9. 迁移策略

不要一口气全部替换,按优先级逐步迁移:

  1. 所有 yield return new WaitForSeconds -> UniTask.Delay
  2. 所有 StartCoroutine / StopCoroutine -> async UniTask / CancellationToken
  3. 需要返回值的异步操作 -> UniTask<T>
  4. 网络请求 + Addressables 加载 -> ToUniTask()
  5. 需要超时控制的场景 -> .Timeout()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 旧代码
private IEnumerator Start()
{
yield return new WaitForSeconds(1);
var req = UnityWebRequest.Get(url);
yield return req.SendWebRequest();
}

// 新代码
private async UniTaskVoid Start()
{
await UniTask.Delay(1000);
var req = await UnityWebRequest.Get(url).SendWebRequest().ToUniTask();
}

总结

UniTask 不是”另一个异步框架”,它是 Unity 中现代异步编程的基础设施:

  • 零 GC 的 struct 状态机
  • 完整的 async/await 支持
  • CancellationToken 标准取消流程
  • 深度 Unity 集成(生命周期、主线程调度)
  • 性能比协程高 50 倍

下一篇将探讨 自定义编辑器扩展,教你构建提升开发效率的工具。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: