unity开发学会UniTask,告别非人性回调写法

简介

Unity 的 UniTask 是一种用于异步编程的 C# 库,它扩展了 .NET 中的 Task 和 await/async 模式。

  1. 与传统的 Task 和 async/await 模式相比,UniTask 更加轻量级,因为它不需要像 Task 一样创建和管理线程池。
  2. UniTask 具有更高的性能,因为它使用了更少的内存和 CPU 资源,几乎0GC消耗。
  3. UniTask 还提供了更多的功能,如取消和超时等功能,这些功能在传统的 Task 中并不容易实现。

Unitask 的常见用法

1、异步等待:使用 await 关键字,可以等待一个异步操作完成,这样就不需要手动处理回调或使用协程。
2、延迟执行:使用 UniTask.Delay 方法可以在指定的时间后执行一个操作,而不需要使用协程或计时器。
3、同步化操作:使用 UniTask.SwitchToMainThread 方法可以在主线程上执行一个操作,这对于访问 Unity 的组件或 API 非常有用。
4、迭代器:使用 UniTask.ToCoroutine 方法可以将一个 UniTask 对象转换为 IEnumerator,从而可以在协程中使用。
5、任务链:使用 UniTask.WhenAll 或 UniTask.WhenAny 方法可以创建一个任务链,等待多个异步操作完成后执行下一步操作。
6、取消任务:使用 UniTaskCancellationToken 类可以取消异步操作,这对于长时间运行的操作非常有用

基础使用案例:

using Cysharp.Threading.Tasks; // 首先需要引入UniTask库

public class UniTaskExample : MonoBehaviour
{
    async void Start()
    {
        // 使用UniTask.Delay进行延迟操作
        await UniTask.Delay(TimeSpan.FromSeconds(1.0f));

        Debug.Log("Waited for 1 second.");
        
        // 异步加载资源
        var www = UnityWebRequest.Get("https://example.com/somefile.txt");
        using (var op = UnityWebRequestAsyncOperation.FromAsync(www.SendWebRequest))
        {
            await op.ToUniTask(); // 将UnityWebRequest的异步操作转换为UniTask
            if (www.result == UnityWebRequest.Result.Success)
            {
                string text = www.downloadHandler.text;
                Debug.Log("Downloaded content: " + text);
            }
            else
            {
                Debug.LogError($"Failed to download: {www.error}");
            }
        }

        // 异步更新UI
        await UniTask.SwitchToMainThread(); // 切换到主线程更新UI
        SomeUIComponent.text = "Updated via UniTask";
    }
}

在上述示例中:

  1. 我们首先展示了如何使用 UniTask.Delay 来实现延迟操作,与 yield return new WaitForSeconds 相比,这种方式不会阻塞主运行线程。

  2. 然后我们演示了如何结合Unity的 UnityWebRequest 异步下载资源,并将其转换为 UniTask,以便使用 await 关键字来等待请求完成。

  3. 最后展示了如何通过 UniTask.SwitchToMainThread 方法将异步任务的结果应用到主线程上的UI元素上,确保UI更新是安全的。

UniTask还可以用于很多其他场景,比如I/O操作、动画等待、游戏逻辑异步化等,能够极大地简化和优化Unity中的异步编程模型。

当然,UniTask 的功能远不止这些基础使用案例。这里再提供一些进阶用法示例:

示例1:并发执行多个任务并等待全部完成

using System.Collections.Generic;
using Cysharp.Threading.Tasks;

public class UniTaskExampleAdvanced : MonoBehaviour
{
    async void Start()
    {
        var tasks = new List<UniTask<string>>(); // 创建一个任务列表来存储异步操作
        
        // 并发启动多个下载任务
        for (int i = 0; i < 5; i++)
        {
            string url = $"https://example.com/resource{i}.txt";
            tasks.Add(DownloadTextAsync(url));
        }

        // 使用UniTask.WhenAll等待所有任务完成
        var results = await UniTask.WhenAll(tasks);

        foreach (var result in results)
        {
            Debug.Log($"Downloaded text: {result}");
        }
    }

    private async UniTask<string> DownloadTextAsync(string url)
    {
        var www = UnityWebRequest.Get(url);
        using (var op = UnityWebRequestAsyncOperation.FromAsync(www.SendWebRequest))
        {
            await op.ToUniTask();
            if (www.result == UnityWebRequest.Result.Success)
            {
                return www.downloadHandler.text;
            }
            else
            {
                Debug.LogError($"Failed to download: {www.error}");
                return null;
            }
        }
    }
}

在这个例子中,我们创建了多个 DownloadTextAsync 异步任务,并使用 UniTask.WhenAll 来等待所有任务都完成。当所有任务完成后,我们可以获取到每个任务的结果。

示例2:取消正在进行的任务

using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;

public class UniTaskCancellationExample : MonoBehaviour
{
    public Button startButton;
    public Button cancelButton;
    private CancellationTokenSource cancellationTokenSource;

    private async void Start()
    {
        startButton.onClick.AddListener(async () => await LongRunningTaskAsync());
        cancelButton.onClick.AddListener(() => CancelLongRunningTask());

        cancellationTokenSource = new CancellationTokenSource();
    }

    private async UniTaskVoid LongRunningTaskAsync()
    {
        try
        {
            Debug.Log("Starting long running task...");
            await UniTask.Delay(TimeSpan.FromSeconds(10), cancellationTokenSource.Token);
            Debug.Log("Long running task completed.");
        }
        catch (OperationCanceledException)
        {
            Debug.LogWarning("Long running task was cancelled.");
        }
    }

    private void CancelLongRunningTask()
    {
        cancellationTokenSource.Cancel();
    }
}

这个示例展示了如何通过传递 CancellationToken 来支持任务的取消。当用户点击“取消”按钮时,我们会调用 cancellationTokenSource.Cancel() 来取消正在运行的长时间任务,如果任务监听到了取消信号,则会抛出 OperationCanceledException 异常,从而可以处理取消逻辑。

示例3:结合Update循环使用UniTask

在Unity中,我们有时需要在Update函数中执行异步操作,并在下一次Update时继续处理。UniTask可以方便地实现这一需求:

using Cysharp.Threading.Tasks;
using UnityEngine;

public class UniTaskWithUpdate : MonoBehaviour
{
    private async UniTaskVoid AsyncProcess()
    {
        for (int i = 0; i < 10; i++)
        {
            Debug.Log($"Processing step {i}...");
            await UniTask.Yield(); // 暂停当前任务,等待下一个Update周期
        }
        Debug.Log("Async process completed.");
    }

    private UniTask runningTask;

    void Update()
    {
        if (runningTask.IsCompleted)
        {
            // 如果上一轮的异步任务已完成,则开始新的任务
            runningTask = AsyncProcess();
            runningTask.ToCoroutine(this); // 将UniTask转换为IEnumerator以配合StartCoroutine
        }
    }
}

在这个示例中,我们在Update函数中启动并跟踪一个异步任务。通过UniTask.Yield(),我们可以在每个异步步骤之间暂停,等待下一次Update调用。

示例4:并发加载多个资源并在完成后显示

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using Cysharp.Threading.Tasks;

public class UniTaskAssetLoadingExample : MonoBehaviour
{
    public List<GameObject> prefabsToLoad;

    async void Start()
    {
        var tasks = new List<UniTask<GameObject>>();

        foreach (var prefabPath in prefabsToLoad.Select(p => p.name))
        {
            tasks.Add(LoadPrefabAsync(prefabPath));
        }

        var loadedPrefabs = await UniTask.WhenAll(tasks);

        foreach (var loadedPrefab in loadedPrefabs)
        {
            Instantiate(loadedPrefab, transform);
        }
    }

    private async UniTask<GameObject> LoadPrefabAsync(string prefabPath)
    {
        var handle = Addressables.LoadAssetAsync<GameObject>(prefabPath);
        return await handle.Task.AsUniTask(cancellationToken: default);
    }
}

扩展插件

默认情况下,UniTask 支持 TextMeshPro(BindTo(TMP_Text)以及TMP_InputField标准 uGUI 等事件扩展InputField)、DOTween(Tween可等待)和 Addressables(AsyncOperationHandle可AsyncOperationHandle等待)。

在单独的 asmdef 中定义了UniTask.TextMeshPro, UniTask.DOTween, UniTask.Addressables。

从包管理器导入包时,会自动启用 TextMeshPro 和 Addressables 支持。但是,对于 DOTween 支持,从DOTWeen 资源导入并定义脚本定义符号后UNITASK_DOTWEEN_SUPPORT即可启用它。
如图:
在这里插入图片描述

// sequential
await transform.DOMoveX(2, 10);
await transform.DOMoveZ(5, 20);

// parallel with cancellation
var ct = this.GetCancellationTokenOnDestroy();

await UniTask.WhenAll(
    transform.DOMoveX(10, 3).WithCancellation(ct),
    transform.DOScale(10, 3).WithCancellation(ct));


Sequence sequence = DOTween.Sequence().Insert(0, m_Camera.transform.DOMove(pos, 1))
                        .Insert(0, m_Camera.transform.DORotate(angle, 1))
                        .OnComplete(() =>
                        {
                              
                           });
await _sequence.AwaitForComplete();

DOTween 支持的默认行为(await, WithCancellation, ToUniTask)等待补间被杀死。它适用于 Complete(真/假)和 Kill(真/假)。但如果想复用tweens( SetAutoKill(false)),就达不到预期的效果了。如果你想等待另一个时机,Tween 中有以下扩展方法,AwaitForComplete, AwaitForPause, AwaitForPlay, AwaitForRewind, AwaitForStepComplete。

这个例子展示了如何利用UniTask来并发加载多个资源,并在所有资源加载完毕后一次性进行处理。这里我们使用了Addressables来进行资源加载,通过UniTask.WhenAll等待所有加载任务完成。

源码地址:https://github.com/Cysharp/UniTask

🎈
🎈
🎈

😍😍 大量H5小游戏、微信小游戏、抖音小游戏源码😍😍
😍😍试玩地址: https://www.bojiogame.sg😍😍
😍看上哪一款,需要源码的csdn私信我😍

🎈
🎈

​最后我们放松一下眼睛
在这里插入图片描述

  • 30
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
Unity中使用Unitask,并支持回调函数传入,可以通过以下步骤实现: 1. 在你的项目中安装Unitask。你可以通过Nuget来进行安装,或者将Unitask的源代码手动添加到你的项目中。 2. 创建一个自定义的TaskCompletionSource类来包装Unitask,并支持回调函数传入。这个类需要实现ITaskCompletionSource接口,同时也要包含一个回调函数的委托属性。 ```csharp public class MyTaskCompletionSource<TResult> : ITaskCompletionSource<TResult> { private TaskCompletionSource<TResult> _tcs = new TaskCompletionSource<TResult>(); private Action<TResult> _callback; public Task<TResult> Task => _tcs.Task; public void SetResult(TResult result) { _tcs.SetResult(result); _callback?.Invoke(result); } public void SetException(Exception exception) { _tcs.SetException(exception); } public void SetCanceled() { _tcs.SetCanceled(); } public void OnCompleted(Action<TResult> continuation) { _callback = continuation; } } ``` 3. 在你的代码中使用自定义的TaskCompletionSource类来包装Unitask。你可以在需要使用回调函数的地方,将回调函数的委托传递给TaskCompletionSource类的OnCompleted方法。 ```csharp public async void MyMethod(Action<int> callback) { MyTaskCompletionSource<int> tcs = new MyTaskCompletionSource<int>(); tcs.OnCompleted(callback); int result = await UniTask.Run(() => { // Do some work... return 42; }); tcs.SetResult(result); } ``` 在上面的代码中,我们创建了一个MyMethod方法,它需要一个回调函数作为参数。我们使用自定义的TaskCompletionSource类来包装了一个UniTask,同时也将回调函数的委托传递给了TaskCompletionSource类的OnCompleted方法。在UniTask完成后,我们调用了TaskCompletionSource类的SetResult方法来设置结果,并在回调函数中调用了委托。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极致人生-010

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值