参考资料:
https://zhuanlan.zhihu.com/p/572670728
https://blog.csdn.net/a598211757/article/details/148453036
UniTask 简介及优势
UniTask 是一个专为 Unity 优化的轻量级异步编程库,由 Cysharp 开发,是 Unity 中处理异步操作的最佳实践方案。
主要特点:
零分配(Zero Allocation):减少 GC 压力,在执行异步操作时不会产生新的内存分配
轻量级:比传统 Task 更轻量
Unity 特定优化:针对 Unity 引擎特性优化
支持所有 Unity 平台:包括 WebGL
UniTask 使用 struct 而不是 class,避免了引用类型的堆内存分配。因此实现了零分配特性。
与协程(Coroutine)对比
语法更现代
使用 async/await 语法,代码更清晰易读(
async/await语法是大多数操作语言处理异步的关键字,冷知识:C#是第一个使用这两个关键字的编程语言)支持返回值,可以直接获取异步操作结果
异常处理更简单,使用
try-catch即可支持取消操作,通过
CancellationToken实现
GC更低
UniTask 实现零分配,不会产生新的内存分配
协程每次
yield都会产生 GC 分配减少垃圾回收压力,提高性能稳定性
特别适合移动平台和性能敏感场景


图源:
https://blog.csdn.net/a598211757/article/details/148453036
耗时更短
执行效率更高,任务调度更快,Release模式Unitask比Coroutine快了7倍以上(来源:
https://www.bilibili.com/video/BV1pF411K7AR )
以下时Unitask和Coroutine的部分代码对比:
// UniTask 方式
async UniTask<int> LoadDataAsync()
{
try
{
await UniTask.Delay(1000); // 延时1000毫秒
return 42; // 支持返回值
}
catch (Exception ex) // 支持try-catch异常处理
{
Debug.LogError(ex);
return -1;
}
}// 协程方式
IEnumerator LoadDataCoroutine()
{
yield return new WaitForSeconds(1f);
// 无法直接返回值
// 异常处理较复杂
}Coroutine也有一些Unitask所不具有的优势,例如其语法简单,易于快速学习掌握。但是在大型项目中,还是更推荐使用UniTask。
与Task相比
UniTask:零分配,使用值类型(struct)实现
Task:每次创建都会产生堆内存分配
UniTask:专为 Unity 优化,原生支持 Unity 特性
Task:通用异步库,对 Unity 特性支持有限
UniTask:基于 Unity 主线程,避免线程切换开销(当然,如果你想,也可以将UniTask切换至线程池)
Task:使用线程池,可能涉及线程切换
UniTask的独家优势
UniTask可以绑定至物体的生命周期,实现物体被销毁,UniTask随之终止。
public class PlayerController : MonoBehaviour
{
async void Start()
{
// 自动绑定到 GameObject 生命周期
await LoadPlayerDataAsync(this.GetCancellationTokenOnDestroy());
}
async UniTask LoadPlayerDataAsync(CancellationToken token)
{
try
{
// 当 GameObject 被销毁时自动取消
await UniTask.Delay(5000, cancellationToken: token);
Debug.Log("Player data loaded");
}
catch (OperationCanceledException)
{
Debug.Log("Loading cancelled due to object destruction");
}
}
}UniTask还有Task所没有的帧同步机制,例如等待一帧、等待固定游戏时间等(用起来和Coroutine差不多):
public class FrameSyncExample : MonoBehaviour
{
async void Start()
{
await FrameSyncOperations();
}
async UniTask FrameSyncOperations()
{
// 等待下一帧
await UniTask.Yield();
// 等待帧结束
await UniTask.WaitForEndOfFrame();
// 等待固定更新
await UniTask.WaitForFixedUpdate();
// 等待指定帧数
await UniTask.DelayFrame(10);
// 等待直到条件满足
await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.Space));
// 等待指定时间(不受时间缩放影响)
await UniTask.WaitForSecondsRealtime(2.0f);
}
}编辑器可视化当前任务执行状态


UniTask Tracker对检查(泄漏的)UniTask很有用。
UniTask 基本语法
将git仓库
async/await
async用来标记一个方法是异步方法,await只能在async方法中使用,await后面只能跟由async标记的方法。这是一个最简单的异步方法:
// async 标记这是一个异步方法
public async UniTask DoWorkAsync()
{
// 方法体内可以使用 await
await UniTask.Delay(1000); // 延时1秒
Debug.Log("Test");
}创建和返回UniTask
可以通过泛型返回值,也可以不返回任何值。
// 无返回值
public async UniTask LoadDataAsync()
{
await UniTask.Delay(1000);
Debug.Log("数据加载完成");
}
// 有返回值
public async UniTask<string> GetUserNameAsync()
{
await UniTask.Delay(500);
return "玩家名称";
}
// 同步方法返回已完成的UniTask
public UniTask<int> GetScoreSync()
{
return UniTask.FromResult(100);
}调用UniTask,可以等待执行,也可以直接执行不做任何处理(使用Forget)。
public async UniTask Example()
{
// 等待无返回值方法
await LoadDataAsync();
// 等待有返回值方法
string userName = await GetUserNameAsync();
// 不等待(Fire and Forget)
LoadDataAsync().Forget();
}时间相关操作
// 延迟1秒
await UniTask.Delay(1000);
// 延迟1秒
await UniTask.Delay(TimeSpan.FromSeconds(1));
// 延迟1秒
UniTask.WaitForSeconds(1f);
// 等待下一帧
await UniTask.NextFrame();
// 等待固定更新
await UniTask.WaitForFixedUpdate();
// 等待帧结束
await UniTask.WaitForEndOfFrame();
// 忽略时间缩放
await UniTask.Delay(1000, ignoreTimeScale: true);
// 指定更新时机
await UniTask.Delay(1000, DelayType.UnscaledDeltaTime);
// 使用取消令牌
await UniTask.Delay(1000, cancellationToken: cts.Token);Delay的各种参数:
ignoreTimeScale参数:时间缩放(Time scale)是否会影响延迟。
DelayType参数:
DeltaTime, // 使用 Time.deltaTime (受timeScale影响)
UnscaledDeltaTime, // 使用 Time.unscaledDeltaTime (不受timeScale影响)
Realtime // 使用真实时间 (完全不受Unity时间影响)
PlayerLoopTiming参数:等待到某个Unity生命周期执行代码
条件等待
满足某些条件后,执行代码。
// 等待条件为真
await UniTask.WaitUntil(() => isReady);
// 等待条件为假
await UniTask.WaitWhile(() => isLoading);
// 等待值变化
await UniTask.WaitUntilValueChanged(transform, x => x.position);并发操作
WhenAll - 等待所有任务完成
public async UniTask LoadAllData()
{
await UniTask.WhenAll(
LoadUserDataAsync(),
LoadGameDataAsync(),
LoadSettingsAsync()
);
Debug.Log("所有数据加载完成");
}
// 带返回值的WhenAll
public async UniTask<(string, int, bool)> GetAllDataAsync()
{
var (userName, score, isVip) = await UniTask.WhenAll(
GetUserNameAsync(),
GetScoreAsync(),
CheckVipStatusAsync()
);
return (userName, score, isVip);
}WhenAny - 等待任意任务完成
public async UniTask<string> GetDataWithTimeout()
{
var (hasResult, result) = await UniTask.WhenAny(
GetDataAsync(),
UniTask.Delay(5000, cancellationToken: cts.Token)
);
if (hasResult)
return result;
else
throw new TimeoutException("获取数据超时");
}取消机制
使用 CancellationToken 可以取消某个UniTask:
public class DataLoader : MonoBehaviour
{
private CancellationTokenSource cts;
void Start()
{
cts = new CancellationTokenSource();
LoadDataAsync(cts.Token).Forget();
}
void OnDestroy()
{
cts?.Cancel();
cts?.Dispose();
}
async UniTask LoadDataAsync(CancellationToken cancellationToken)
{
try
{
await UniTask.Delay(3000, cancellationToken: cancellationToken);
Debug.Log("数据加载完成");
}
catch (OperationCanceledException)
{
Debug.Log("加载被取消");
}
}
}取消时使用try-catch块进行异常处理,或使用
SuppressCancellationThrow。否则会报OperationCanceledException错误。public async UniTask SafeOperation() { // 不抛出取消异常,返回bool表示是否被取消 bool cancelled = await UniTask.Delay(1000, cancellationToken: cts.Token) .SuppressCancellationThrow(); if (cancelled) { Debug.Log("操作被取消,但不抛异常"); return; } Debug.Log("操作完成"); }

评论区