UniTask基础说明

Unity 异步编程 基础使用

1.前言

先前开发游戏过程中,有用到Zenject + UniTask + UniRx + Localization + GoogleSheet + Addressable的方式来开发游戏,后续会逐个介绍,本篇进行UniTask的介绍,希望起到抛砖引玉作用。
传统的异步编程通常使用 Coroutine 或 Task,但它们各有优缺点。UniTask 是一个专为Unity设计的高性能异步编程库,它结合了 Coroutine 和 Task 的优点,提供了更简洁、更高效的异步编程方式。本文将介绍一下UniTask的基础。

2.UniTask 的好处

  • 高性能:UniTask 是专为Unity设计的,性能比 Task 更高,特别是在频繁创建和销毁任务的场景中。
  • 简洁的语法:UniTask 提供了类似于 async/await 的语法,使得异步代码更加简洁和易读。
    与Unity无缝集成:UniTask 支持 UnityEngine 的各种异步操作,如 WaitForSeconds、WaitForEndOfFrame 等。
  • 丰富的功能:UniTask 提供了许多高级功能,如延续操作、取消和异常处理、超时处理等。

UniTask 的常用语法基础

可以将类型返回为 struct UniTask(或 UniTask),它是 Task 的Unity专用轻量级替代方案

//实现0开销(0GC和快速执行)的async/await Unity集成
async UniTask<string> DemoAsync()
{
 //可以await Unity async对象
 var asset = await Resources.LoadAsync<TextAsset>("foo");
 var txt = (await UnityWebRequest.Get("https://...").SendWebRequest()).downloadHandler.text;
 await SceneManager.LoadSceneAsync("scene2");

 //.WithCancellation 启用取消方法,GetCancellationTokenOnDestroy 与 GameObject 的生命周期同步
 var asset2 = await Resources.LoadAsync<TextAsset>("bar").WithCancellation(this.GetCancellationTokenOnDestroy());

 //.ToUniTask 接受进度回调(和完整的参数),Progress.Create 是 IProgress<T> 的轻量级替代品
 var asset3 = await Resources.LoadAsync<TextAsset>("baz").ToUniTask(Progress.Create<float>(x => Debug.Log(x)));

 //像协程一样,是await基于帧的操作
 await UniTask.DelayFrame(100); 

 //替换 yield return new WaitForSeconds/WaitForSecondsRealtime
 await UniTask.Delay(TimeSpan.FromSeconds(10), ignoreTimeScale: false);
 
 //产生任何播放器循环时间(PreUpdate、Update、LateUpdate 等...)
 await UniTask.Yield(PlayerLoopTiming.PreLateUpdate);

 //替换 yield return null
 await UniTask.Yield();
 await UniTask.NextFrame();

 //替换 WaitForEndOfFrame(需要 MonoBehaviour(CoroutineRunner))
 await UniTask.WaitForEndOfFrame(this); //这是 MonoBehaviour
 //替换 yield return new WaitForFixedUpdate(同 UniTask.Yield(PlayerLoopTiming.FixedUpdate))
 await UniTask.WaitForFixedUpdate();
 
 //替换 yield return WaitUntil
 await UniTask.WaitUntil(() => isActive == false);

 //WaitUntil 的帮助方法
 await UniTask.WaitUntilValueChanged(this, x => x.isActive);

 //可以await IEnumerator 协程
 await FooCoroutineEnumerator();

 //可以await C#标准Task
 await Task.Run(() => 100);

 //多线程,此代码下运行在 ThreadPool 上
 await UniTask.SwitchToThreadPool();

 /*在线程池上工作*/
 //返回主线程(与 UniRx 中的 `ObserveOnMainThread` 相同)
 await UniTask.SwitchToMainThread();

 //获取async网络请求
 async UniTask<string> GetTextAsync(UnityWebRequest req)
    {
 var op = await req.SendWebRequest();
 return op.downloadHandler.text;
    }

 var task1 = GetTextAsync(UnityWebRequest.Get("http://google.com"));
 var task2 = GetTextAsync(UnityWebRequest.Get("http://bing.com"));
 var task3 = GetTextAsync(UnityWebRequest.Get("http://yahoo.com"));

 //并发async await并通过元组语法轻松获取结果
 var (google, bing, yahoo) = await UniTask.WhenAll(task1, task2, task3);

 //WhenAll的简写,tuple可以直接await
 var (google2, bing2, yahoo2) = await (task1, task2, task3);

 //返回async值。(或者您可以使用 `UniTask`(无结果)、`UniTaskVoid`(即发即弃))。
 return (asset as TextAsset)?.text ?? throw new InvalidOperationException("Asset not found");
}

3.UniTask 的常用语法晋级

3.1 延续操作(返回值元组)

1.UniTask.WhenAll(全部完成后)
2.UniTask.WhenAny(任一完成后)

public async UniTaskVoid LoadManyAsync()
{
    // 并行加载.
    var (a, b, c) = await UniTask.WhenAll(
        LoadAsSprite("foo"),
        LoadAsSprite("bar"),
        LoadAsSprite("baz"));
}

延续操作,也可通过返回值元组来实现

//同时返回
 private async UniTask<(int, string)> PerformTasks()
    {
        int result1 = await Task1();
        string result2 = await Task2();
        return (result1, result2);
    }

    private async UniTask<int> Task1()
    {
        await UniTask.Delay(1000);
        return 42;
    }

    private async UniTask<string> Task2()
    {
        await UniTask.Delay(2000);
        return "Hello, UniTask!";
    }

3.2 取消和异常处理

UniTask 支持任务的取消和异常处理,可以使用 CancellationToken 和 try catch 语句来实现。

private void Start()
    {
        _cts = new CancellationTokenSource();
        PerformTask(_cts.Token).Forget();
    }

    private void OnDestroy()
    {
        _cts.Cancel();
    }
	//支持try  catch 
    private async UniTaskVoid PerformTask(CancellationToken token)
    {
        try  
        {
            await UniTask.Delay(5000, cancellationToken: token);
            Debug.Log("Task completed");
        }
        catch (OperationCanceledException)
        {
            Debug.Log("Task was cancelled");
        }
    }

CancellationToken表示异步的生命周期

一些UniTask工厂方法有一个CancellationToken cancellationToken = default参数。此外,Unity的一些异步操作有WithCancellation(CancellationToken)和ToUniTask(…, CancellationToken cancellation = default)扩展方法
下边是个标准的CancellationTokenSource用法

var cts = new CancellationTokenSource();

cancelButton.onClick.AddListener(() =>
{
    cts.Cancel();
});

await UnityWebRequest.Get("http://www.baidu.com").SendWebRequest().WithCancellation(cts.Token);

await UniTask.DelayFrame(1000, cancellationToken: cts.Token);

3.3超时处理

UniTask 提供了超时处理功能,使用 Timeout ,CancelAfterSlim,CreateLinkedTokenSource ,TimeoutController ,等来实现。
下边是简单 Timeout 的使用

 private async void Start()
    {
        try
        {
            await PerformTask().Timeout(TimeSpan.FromSeconds(3));
            Debug.Log("Task completed");
        }
        catch (TimeoutException)
        {
            Debug.Log("Task timed out");
        }
    }
    private async UniTask PerformTask()
    {
        await UniTask.Delay(5000);
    }

3.4返回值(*)

UniTask 支持多种返回值类型,包括 void、UniTask、UniTask 等等。
下边是个简单的示例

private async void Start()
    {
        await PerformVoidTask();        
        int result = await PerformIntTask();
        Debug.Log($"Result: {result}");
    }

    private async UniTaskVoid PerformVoidTask()
    {
        await UniTask.Delay(1000);
        Debug.Log("Void task completed");
    }
	//进行Int类型的返回
    private async UniTask<int> PerformIntTask()
    {
        await UniTask.Delay(2000);
        return 42;
    }

3.5注册到Event的异步委托(lambda)

UniTask 支持将异步委托注册到事件中,可以使用 async 关键字来实现。

public event Func<UniTask> OnTaskRequested;

    private async void Start()
    {
    	//进行委托的注入
        OnTaskRequested += async () =>
        {
            await UniTask.Delay(1000);
            Debug.Log("Event task completed");
        };

        if (OnTaskRequested != null)
        {
            await OnTaskRequested.Invoke();
        }
    }

不要使用async void。可以使用UniTask.Action或UniTask.UnityAction,它们都通过async UniTaskVoid lambda创建一个委托。

// 这是不好的: async void
actEvent += async () => { };
unityEvent += async () => { };

// 这是好的: 通过lamada创建Action
actEvent += UniTask.Action(async () => { await UniTask.Yield(); });
unityEvent += UniTask.UnityAction(async () => { await UniTask.Yield(); });

3.6委托中生成UniTask的工厂方法

当然也 可以在委托中使用工厂方法来生成UniTask。
使用工厂方法来生成UniTask,并在委托中调用该方法,下边是示例:

 private async void Start()
    {
        Func<UniTask> taskFactory = () => PerformTask();
        await taskFactory.Invoke();
    }

    private async UniTask PerformTask()
    {
        await UniTask.Delay(1000);
        Debug.Log("Factory task completed");
    }

4.UniTask 注意事项

  • 性能:虽然UniTask性能较高,但在频繁创建和销毁任务的场景中,仍需注意性能优化。
  • 取消和异常处理:在使用取消和异常处理时,需要确保正确处理 CancellationToken 和异常,以避免内存泄漏和未处理的异常。
  • 与Unity主线程的交互:在异步任务中操作Unity对象时,需要确保在主线程中执行,可以使用 UniTask.SwitchToMainThread 方法来切换到主线程。
  • IEnumerator.ToUniTask 限制,不支持WaitForEndOframe/WaitForFixedUpdate/Coroutine
    生命周期与StartCoroutine不一样,它使用指定的PlayerLoopTiming,默认的PlayerLoopTiming.Update会在MonoBehaviour的Update和StartCoroutine的循环之前运行。
  • 部分平台兼容较差,比如微信小游戏平台

5.总结

通过本文的介绍,相信大家对UniTask有了一个全面的了解。UniTask 是一个非常强大的异步编程库,可以帮助我们更高效地编写异步代码,提高代码的可读性和可维护性。希望大家在实际项目中能够灵活应用UniTask,写出高质量的代码,新人创作不易,谢谢大家。

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值