在我们介绍UniTask之前,先来复习一下协程。

异步加载的演变

核心前置知识点

  • 同步代码:一行执行完才走下一行,会卡主线程
  • 异步代码:不阻塞主线程,等待 IO / 延时 / 网络时主线程继续渲染(玩家可移动)

异步加载的演变

  • Unity传统异步方案:协程Coroutine (IEnumerator)
  • C#原生异步:async/await + Task
  • 第三方优化方案:UniTask

协程

1
2
3
4
IEnumerator TestCoroutine()
{
yield return new WaitForSeconds(1);
}

Task(async/await)

1
2
3
4
5
async Task<int> GetResultAsync()
{
await Task.Delay(1000); // 模拟异步操作
return 42; // 返回结果
}

异步方法的返回类型通常是TaskTask<T>,其中Task表示异步操作,Task<T>表示异步操作完成后返回一个类型为T的结果。

在异步操作中,可能会遇到需要取消任务的情况,例如用户取消操作或超时。C#提供了CancellationToken来支持任务的取消机制。通过传递取消令牌,提高程序的灵活性和响应性。

创建取消令牌:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public async Task MainAsync()
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

Task task = DoWorkAsync(token);

// 模拟用户取消操作
await Task.Delay(500);
cts.Cancel();

try
{
await task; // 等待任务完成
}
catch(OperationCanceledException ex)
{
// 处理取消异常
Console.WriteLine("任务取消");
}
}

UniTask

UniTask 是专为Unity引擎打造的高性能、零GC异步编程库。


GC是什么?我们先来回顾一下GC的概念。

GC(Garbage Collection),顾名思义,他是垃圾回收的意思。

我们都知道,内存分为两块:栈、堆。

  • 栈内存:速度极快、空间小、自动分配自动释放。
    • 存:int、float、bool、结构体(struct)、局部变量。
    • 用完立刻自己消失,完全不用 GC 管。
  • 堆内存:空间大、速度慢、需要人管理。
    • 存:所有 class 类对象、new 出来的东西、字符串、数组、集合。

GC在垃圾回收的时候做了三件事:

  • 标记:遍历所有内存,标记哪些对象还在被使用,哪些已经废弃。
  • 清除:把废弃的垃圾对象直接删掉,释放内存。
  • 整理(压缩):把零散空闲内存拼在一起,避免内存碎片。

当堆内存不够用了或者达到系统阈值时,就会触发GC。

但是现在有一个关键问题:GC工作时,会短暂暂停游戏主线程,表现就是:瞬间卡顿、掉帧。而协程的一大痛点在于:每次yield return null或者返回别的什么,都要经历一次装箱,也就是把值类型装成引用类型的object,这样会频繁触发GC,导致卡顿。

而UniTask基于值(struct)设计,零GC,解决Unity异步开发的核心痛点。

使用UniTask在 Window - Package Manager - 左上角+号 - Add package from git URL - 输入UniTask的Git地址:

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

下载UniTask导入项目:

  • 需要引用命名空间
1
using Cysharp.Threading.Tasks;
  • UniTask
1
2
3
4
async UniTask TestUniTask()
{
await UniTask.WaitForSeconds(1);
}

除了这种标准用法外,我们还需要注意如果想在一个不是异步的方法中使用异步方法,我们需要用到.Forget()

例如,当我们加载一个场景时,可能会这样写:

1
2
3
4
private void Start()
{
LoadStartScene().Forget();
}

其中,LoadStartScene()是一个异步方法,.Forget()表示不等待它执行完。

正常来说可以这样写:

1
await LoadStartScene();

但在 Unity 的Start() 不是 async 方法,不能直接写 await。所以用了 .Forget()