侧边栏壁纸
博主头像
LittleAO的学习小站 博主等级

在知识的沙漠寻找绿洲

  • 累计撰写 125 篇文章
  • 累计创建 27 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Unitask 简单使用手册

LittleAO
2025-06-17 / 0 评论 / 0 点赞 / 20 阅读 / 0 字
温馨提示:
本文最后更新于2025-06-17,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

参考资料:

https://zhuanlan.zhihu.com/p/572670728

https://blog.csdn.net/a598211757/article/details/148453036

https://www.i3dtt.com/163833.html

https://github.com/Cysharp/UniTask

UniTask 简介及优势

UniTask 是一个专为 Unity 优化的轻量级异步编程库,由 Cysharp 开发,是 Unity 中处理异步操作的最佳实践方案。

主要特点:

  • 零分配(Zero Allocation):减少 GC 压力,在执行异步操作时不会产生新的内存分配

  • 轻量级:比传统 Task 更轻量

  • Unity 特定优化:针对 Unity 引擎特性优化

  • 支持所有 Unity 平台:包括 WebGL

UniTask 使用 struct 而不是 class,避免了引用类型的堆内存分配。因此实现了零分配特性。

与协程(Coroutine)对比

  1. 语法更现代

  • 使用 async/await 语法,代码更清晰易读(async/await 语法是大多数操作语言处理异步的关键字,冷知识:C#是第一个使用这两个关键字的编程语言)

  • 支持返回值,可以直接获取异步操作结果

  • 异常处理更简单,使用 try-catch 即可

  • 支持取消操作,通过 CancellationToken 实现

  1. GC更低

  • UniTask 实现零分配,不会产生新的内存分配

  • 协程每次 yield 都会产生 GC 分配

  • 减少垃圾回收压力,提高性能稳定性

  • 特别适合移动平台和性能敏感场景

图源:https://blog.csdn.net/a598211757/article/details/148453036

  1. 耗时更短

以下时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的独家优势

  1. 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");
        }
    }
}
  1. 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);
    }
}
  1. 编辑器可视化当前任务执行状态

UniTask Tracker对检查(泄漏的)UniTask很有用。

UniTask 基本语法

将git仓库https://github.com/Cysharp/UniTask添加到项目的Package Manager,将UniTask引入项目后便可以使用相应功能。

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("操作完成");
}

0

评论区