Unity入门教程之异步篇第一节:协程基础扫盲–非 Mono 类如何也能启动协程?-CSDN博客
Unity入门教程之异步篇第二节:协程 or UniTask?Unity 中异步流程到底怎么选-CSDN博客
Unity入门教程之异步篇第三节:多线程初探?理解并发与线程安全-CSDN博客
Unity入门教程之异步篇第四节:Unity 高性能计算?Job System 与 Burst Compiler !-CSDN博客
Unity入门教程之异步篇第五节:对UniTask的高级封装-CSDN博客
Unity入门教程之异步篇第六节:对Job System的高级封装
为什么要对 UniTask 进行二次封装?
在开始前,我们首先需要回答这个问题,我们为什么要对UniTask进行二次封装?回答是:UniTask 提供了底层的异步操作原语,就像搭乐高的基础砖块。然而,直接使用这些基础砖块在大型或复杂的项目中可能会遇到一些挑战:
-
提高易用性和开发效率:
- 减少重复代码: 许多异步操作模式在项目中会反复出现,例如:等待一段时间、加载文件、网络请求等。如果每次都从头编写
UniTask.Delay、await UniTask.SwitchToThreadPool()等,会产生大量重复且相似的代码。 - 统一接口:
UniTask本身有多种等待方式(按时间、按帧、等待条件),通过封装可以提供一个更统一、更语义化的接口。 - 隐藏底层细节: 封装可以将线程切换、取消令牌管理、异常处理等底层复杂性隐藏起来,让业务逻辑开发者能更专注于他们要实现的功能,而不是异步编程的细节。
- 减少重复代码: 许多异步操作模式在项目中会反复出现,例如:等待一段时间、加载文件、网络请求等。如果每次都从头编写
-
增强代码可读性和可维护性:
- 语义化命名: 通过封装,我们可以用更具业务含义的名称来命名异步操作,例如
DownloadAssetAsync、FadeOutUI,而不是泛泛的DoSomethingAsync。 - 降低心智负担: 当你看到
await MyTaskUtils.WaitFor(2.5f)时,立刻就知道它是在等待,而不需要去思考UniTask.Delay内部参数的含义。 - 集中修改: 如果未来
UniTask某个 API 的使用方式发生变化,或者需要统一修改某个异步行为(例如统一的日志输出),只需要修改封装层,而不需要修改所有使用到该 API 的地方。
- 语义化命名: 通过封装,我们可以用更具业务含义的名称来命名异步操作,例如
-
提升代码健壮性和安全性:
- 统一异常处理: 异步操作中的异常处理往往容易被忽略。封装层可以内置
try-catch块,确保所有异步任务的异常都能被捕获和处理,避免程序崩溃。 - 自动取消管理:
CancellationToken是UniTask的核心,但手动管理CancellationTokenSource的生命周期容易出错。封装可以自动绑定任务与MonoBehaviour的生命周期,防止内存泄漏和僵尸任务。 - 线程安全: 封装可以确保敏感操作(如文件 IO)在正确的线程(后台线程)上执行,并在必要时安全地切回主线程,避免多线程访问 Unity API 的错误。
- 统一异常处理: 异步操作中的异常处理往往容易被忽略。封装层可以内置
如何对 UniTask 进行二次封装(代码示例与设计意图)
我们将通过四个常用场景来展示如何进行二次封装,涵盖了延迟等待、文件 IO、周期性任务和生命周期绑定。
1. 统一的延迟等待方法:MyTaskUtils.WaitFor
代码:
using Cysharp.Threading.Tasks;
using System.Threading;
using System;
using UnityEngine;
public static class MyTaskUtils
{
/// <summary>
/// 异步等待指定的时间(秒)或帧数。
/// 默认在主线程更新。
/// </summary>
/// <param name="delayTime">等待时间(秒)。如果小于等于 0,则等待指定帧数。</param>
/// <param name="delayFrames">如果 delayTime <= 0,则等待此帧数。</param>
/// <param name="ignoreTimeScale">是否忽略 Time.timeScale 的影响。</param>
/// <param name="cancellationToken">可选的取消令牌。</param>
/// <returns>UniTask。</returns>
public static async UniTask WaitFor(
float delayTime,
int delayFrames = 1,
bool ignoreTimeScale = false,
CancellationToken cancellationToken = default)
{
if (delayTime > 0)
{
await UniTask.Delay(TimeSpan.FromSeconds(delayTime), ignoreTimeScale, PlayerLoopTiming.Update, cancellationToken);
}
else
{
await UniTask.DelayFrame(delayFrames, PlayerLoopTiming.Update, cancellationToken);
}
}
}
设计意图和写法:
- 单一职责: 这个方法旨在提供一个统一的“等待”功能。在 Unity 中,我们常常需要按时间等待(受
Time.timeScale影响或不影响),或者按帧等待。UniTask提供了Delay和DelayFrame两个独立的方法来处理。 - 参数合并与智能判断: 我将
delayTime和delayFrames合并到一个方法签名中。通过判断delayTime是否大于 0,智能地选择调用UniTask.Delay或UniTask.DelayFrame,简化了调用方的心智负担。 - 默认参数:
delayFrames默认为 1,ignoreTimeScale默认为false,cancellationToken默认为default。这使得最常见的“等待一小段时间”或“等待一帧”的调用变得非常简洁,例如await WaitFor(2.5f);或await WaitFor(0);。 PlayerLoopTiming.Update: 明确指定了在Update阶段进行等待,这是最常用的游戏逻辑更新时机。- 可取消性: 依然保留了
cancellationToken参数,确保封装后的方法仍然支持任务取消,这是UniTask的核心优势之一。
2. 安全的文件 IO 操作:FileIOTaskUtils
代码:
using Cysharp.Threading.Tasks;
using System.IO;
using System.Text;
using System.Threading;
using


2183

被折叠的 条评论
为什么被折叠?



