Unity插件之UniTask
在我们介绍UniTask之前,先来复习一下协程。
协程与异步加载
卡顿的根源在于一帧里干的事太多,CPU算不完,画面就会卡住,而协程就是将一大堆耗时任务拆开分到很多帧慢慢做,不挤在同一帧。
比如说,假设你要一次性加载100张图片,
普通写法是Update或普通函数里一帧直接循环加载100张,这一帧CPU瞬间爆满,画面直接卡死、掉帧。
协程写法就是一帧只加载5张,然后yield return null让出这一帧剩余时间,下一帧在接着加载5张,把压力分摊到几十帧里,每一帧工作量都很小,就不会卡。
yield return null返回出这一帧剩余的时间后不是什么都不干,而是将CPU时间让出去,这时候这些时间CPU可能会给:
- 其他所有脚本的
Update - 其他正在跑的协程
- 等等。。
需要注意的是:协程并不能让加载变快,而是避免游戏卡死,这里可以举一个例子来区分二者:
- 情况A:不分帧,一帧怼完
- 游戏画面直接卡死、黑屏、鼠标不动、UI 点不了
- 情况B:协程分帧慢慢加载
- 游戏画面照常刷新、UI 能动、按钮能点、动画在播、背景音乐不停
那要怎么才能真正提高游戏的加载速度呢?异步加载。
异步加载就是说页面不用等某个资源加载完,再继续渲染后面内容,而是在后台悄悄加载,加载完成后再回调使用,不阻塞主线程、不卡住画面。
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; |
核心规则:
- 异步方法返回值:
async UniTask或者async UniTask<T> - 内部方法用
await - 物体销毁时用
GetCancellationTokenOnDestory()防内存泄漏
- 异步方法返回值:
延时等待
1 | // 等待1秒,受 TimeScale 影响 |
- 等待下一帧、等待 N 帧
1 | await UniTask.Yield(); // 等下一帧 |






