记录关于Unitask的一些基础使用

前言

由于最近编写一下unity的游戏框架,使用了unity自带的协程去做一些异步操作,发现有很多限制,也需要提升下代码质量,所以为稍微学习一下UniTask的使用。UniTask插件使用了懒加载的方式实现,在第一次运行一些异步操作的时候,效率会稍微比协程慢,但是之后性能消耗会是协程消耗的十分之一上下。

一、Unitask插件Github路径

https://github.com/Cysharp/UniTask?tab=readme-ov-file#install-via-git-url

使用之前需要在对应的CS文件上面引用命名空间using Cysharp.Threading.Tasks不然会没办法扩展C# await/aysnc的功能

二、基本使用方法记录

1.文本异步加载

代码如下(示例):

Mono托管

public class UniTaskBaseTest : MonoBehaviour
{
    private Text textTest;
    private async void LoadTextTest()
    {
        var loadOperation = Resources.LoadAsync<TextAsset>("test");
        var text = await loadOperation;
        textTest.text = ((TextAsset)text).text;
    }
}

非Mono托管

public class UniTaskBaseTest : MonoBehaviour
{
    private Text textTest;
    private async void LoadTextTest()
    {
        UniTaskBaseTest01 uniTaskBaseTest01 = new UniTaskBaseTest01();
        var textAsset = await uniTaskBaseTest01.LoadAsync<TextAsset>("TextAsset");
        textTest.text = ((TextAsset)textAsset).text;
    }
}

public class UniTaskBaseTest01
{
    public async UniTask<Object> LoadAsync<T>(string path)
    {
        var loadOperation = Resources.LoadAsync<Object>(path);
        return await loadOperation;
    }
}

2.加载场景的运用

代码如下(示例):

public class UniTaskBaseTest : MonoBehaviour
{
    private async void LoadSceneAsync()
    {
        await SceneManager.LoadSceneAsync("Scene/Map01").ToUniTask(
            (Progress.Create<float>((p) =>
            {
                Debug.Log(p * 100);
            })));
    }
}

3.请求下载图片并且切换成Sprite动画

public class UniTaskBaseTest : MonoBehaviour
{
    private async void WebTextureDownload()
    {
        try
        {
            var webRequest = 
                UnityWebRequestTexture.GetTexture("https://i0.hdslb.com/bfs/static/jinkela/video/asserts/33-coin-ani.png");
            var result = await webRequest.SendWebRequest();
            var texture = ((DownloadHandlerTexture) result.downloadHandler).texture;
            int totalSpriteCount = 24;
            int perSpriteWidth = texture.width / totalSpriteCount;
            Sprite[] sprites = new Sprite[totalSpriteCount]; 
            for (int i = 0; i < totalSpriteCount; i++)
            {
                sprites[i] = Sprite.Create(texture, new Rect(new Vector2(perSpriteWidth * i,0),new Vector2(perSpriteWidth,texture.height)), new Vector2(0.5f,0.5f));
            }
            
            float perFrame = 0.1f;
            while (true)
            {
                for (var i = 0; i < totalSpriteCount; i++)
                {
                    await UniTask.Delay(TimeSpan.FromSeconds(perFrame));
                    var sprite = sprites[i];
                    // todo ..
                }
            }
        }
        catch (Exception e)
        {
            throw; // TODO handle exception
        }
    }
}

4.UniTask.Delay

I.简单的按秒数延时时间

public class UniTaskBaseTest : MonoBehaviour
{
    public async void Start()
    {
        Debug.Log($"执行Delay前时间{Time.time}");
        await UniTask.Delay(TimeSpan.FromSeconds(2));
        Debug.Log($"执行Delay后时间{Time.time}");
    }
}

II.简单按帧数延时时间

public class UniTaskBaseTest : MonoBehaviour
{
    public async void Start()
    {
        Debug.Log($"执行Delay前时间{Time.frameCount}");
        await UniTask.DelayFrame(5);
        Debug.Log($"执行Delay后时间{Time.frameCount}");
    }
}

5.UniTask.NextFrame\WaitForEndOfFrame\Yield

写入注入代码的案例,测试下这些函数的执行时机

public class UniTaskBaseTest : MonoBehaviour
{
    public bool showUpdateLog = false;
    public List<PlayerLoopSystem.UpdateFunction> injectUpdateFunctions = new List<PlayerLoopSystem.UpdateFunction>();
    private UniTaskAsyncSmaple_Wait uniTaskAsyncWaiter = new UniTaskAsyncSmaple_Wait();
    public PlayerLoopTiming playerLoopTiming;
    private void InjectFunction()
    {
        PlayerLoopSystem playerLoop = PlayerLoop.GetCurrentPlayerLoop();
        var subSystem = playerLoop.subSystemList;
        playerLoop.updateDelegate += OnUpdate;
        for (int i = 0; i < subSystem.Length; i++)
        {
            int index = i;
            PlayerLoopSystem.UpdateFunction injectFunction = () =>
            {
                if (!showUpdateLog)
                {
                    return;
                }
                
                Debug.Log($"执行子系统{showUpdateLog} {subSystem} 当前帧数 {Time.frameCount}");
            };
            injectUpdateFunctions.Add(injectFunction);
            subSystem[index].updateDelegate += injectFunction;
        }
        PlayerLoop.SetPlayerLoop(playerLoop);
    }

    private async void TestNextFrame()
    {
        showUpdateLog = true;
        Debug.Log("执行NextFrame开始");
        await uniTaskAsyncWaiter.WaitNextFrame();
        Debug.Log("执行NextFrame开始");
        showUpdateLog = false;
    }
    
    private async void TestEndOfFrame()
    {
        showUpdateLog = true;
        Debug.Log("执行EndOfFrame开始");
        await uniTaskAsyncWaiter.WaitEndOfFrame();
        Debug.Log("执行EndOfFrame开始");
        showUpdateLog = false;
    }
    
    private async void TestYield()
    {
        showUpdateLog = true;
        Debug.Log("执行Yield开始");
        await uniTaskAsyncWaiter.WaitYield(playerLoopTiming);
        Debug.Log("执行Yield开始");
        showUpdateLog = false;
    }

    private void OnUpdate()
    {
        
    }
}

public class UniTaskAsyncSmaple_Wait
{
    public async UniTask<int> WaitYield(PlayerLoopTiming loopTiming)
    {
        await UniTask.Yield(loopTiming);
        return 0;
    }

    public async UniTask<int> WaitNextFrame()
    {
        await UniTask.NextFrame();
        return 0;
    }

    public async UniTask<int> WaitEndOfFrame()
    {
        await UniTask.WaitForEndOfFrame();
        return 0;
    }
}

测试打印
在这里插入图片描述
在这里插入图片描述

经过测试得知,NextFrame会等待到下一帧Update时机结束之后,而EndOfFrame会到下一帧初始化(Initialization)之前,Yield可以自行更改时机

6.Unitask WhenAll和WhenAny使用方法

假设有个监测1为check1,监测2为check2,则下面就是等待如下代码check1和check2都完成的时机的代码

public class UniTaskBaseTest : MonoBehaviour
{
    private async void WhenAllTest()
    {
        var check1 = UniTask.WaitUntil(() => true);
        var check2 = UniTask.WaitUntil(() => true);
        await UniTask.WhenAll(check1,check2); 
        Debug.Log("条件完成");
    }
}

当只需要其中一个条件完成的情况下就可以的情况下,直接使用WhenAny方法就可以了

public class UniTaskBaseTest : MonoBehaviour
{
    private bool c1 = true;
    private bool c2 = true;
    private async void WhenAllTest()
    {
        var check1 = UniTask.WaitUntil(() => c1);
        var check2 = UniTask.WaitUntil(() => c2);
        await UniTask.WhenAny(check1,check2); 
        Debug.Log($"任意一个完成就行c1:{c1},c2:{c2}");
    }
}

7.UniTask取消

UniTask给我们设计了一种非常好用的取消方式,使用对应的token进行取消。

public class UniTaskBaseTest : MonoBehaviour
{
    private CancellationTokenSource cts1 = new CancellationTokenSource(); 

    public async void Task1()
    {
        try
        {
           await TestTask1(cts1.Token);
        }
        catch (OperationCanceledException e)
        {
            throw;
        }
    }

    private async UniTask TestTask1(CancellationToken token)
    {
        while (true)
        {
            await UniTask.NextFrame(token);
        } 
    } 

   private void CtsCancel()
   {
       cts1.Cancel();
       cts1.Dispose();
   }
} 

这里通过捕获异常的时候进行取消的操作,当然这样会有些性能的消耗。这样可以通过SuppressCancellationThrow的方式来取消掉异常捕获即可

public class UniTaskBaseTest : MonoBehaviour
{
    private CancellationTokenSource cts2 = new CancellationTokenSource(); 
    public async void Task2()
    {
        var (cancelled,_) = await TestTask2(cts2.Token).SuppressCancellationThrow();
        if (cancelled)
        {
            //todo ..
        }
    }

    private async UniTask<int> TestTask2(CancellationToken token)
    {
        while (true)
        {
            await UniTask.NextFrame(token);
        } 
    }
    
    private void CtsCancel()
    {
        cts2.Cancel();
        cts2.Dispose();
    }
} 

使用token结束后记得手动调用Dipose~。

二、UniTask扩展

1.网络请求超时操作

使用UniTask来处理一些网络超时问题,设置一个期望时间,如果超过这个期望时间就使用token取消操作

public class TimeOutTest : MonoBehaviour
{
   public string SearchWorld = "Unity";

   public string[] SerachURLs = new string[]
   {
      "https://www.baidu.com/s?wd=",
      "https://www.bing.com/search?q=",
      "https://www.google.com/search?wd=",
   };

   private Button TestButton;
   private void Start()
   {
      TestButton = GameObject.Find("TestButton").GetComponent<Button>();
      TestButton.onClick.AddListener(UniTask.UnityAction(OnClickTest));
   }

   private async UniTask<string> GetRequest(string url,float timeout)
   {
      var cts = new CancellationTokenSource();
      cts.CancelAfter(TimeSpan.FromSeconds(timeout));

      var (cancelOrFailed, result) = await UnityWebRequest.Get(url).SendWebRequest().WithCancellation(cts.Token).SuppressCancellationThrow();
      if (!cancelOrFailed)
      {
         return result.downloadHandler.text;
      }

      return "取消或超时操作";
   }

   private async UniTaskVoid OnClickTest()
   {
      UniTask<string>[] awaitTasks = new UniTask<string>[SerachURLs.Length];
      for (int i = 0; i < SerachURLs.Length; i++) {
         awaitTasks[i] = GetRequest(SerachURLs[i],2f);
      }
      
      var tasks = await UniTask.WhenAll(awaitTasks);
      for (int i = 0; i < awaitTasks.Length; i++) {
         Debug.Log(tasks[i]);
      }
   }

2.小球掉落案例

I.同步方法使用异步方法的方案(Forget方法使用)

代码和场景如下

public class UniTaskTest : MonoBehaviour
{
    public float G = 9.8f;
    public Transform prefab1;
    public Transform prefab2;
    public float FallTime = 2f;
    public Button StartButton;

    private void Start()
    {
        StartButton.onClick.AddListener(OnClickStart);
    }

    private void OnClickStart()
    {
        FallTarget(prefab1,FallTime).Forget();
        FallTarget(prefab2,FallTime).Forget();
    }

    private async UniTaskVoid FallTarget(Transform targetTransform, float fallTime)
    {
        float startTime = Time.time;
        Vector3 startPos = targetTransform.position;
        while (Time.time - startTime < fallTime)
        {
            float elapsedTime = Mathf.Min(Time.time - startTime, fallTime);
            float fallY = 0 + 0.5f * G * elapsedTime * elapsedTime;
            targetTransform.position = Vector3.Lerp(startPos, startPos + Vector3.down * fallY, elapsedTime);
            await UniTask.Yield(this.GetCancellationTokenOnDestroy());
        }
    } 
} 

在这里插入图片描述
点击开始掉落按钮,两个小球会按照自由落体公式进行掉落。在OnClickStart方法里面使用了Forget方法在同步方法进行异步调用。

II.UniTask回调添加(手动完成UniTask的任务)

这里稍微修改一下上面的代码,代码设计成,当小球掉落到一半的时间的时候,缩放变成原来的1.5倍。这里使用UniTaskCompletionSource进行回调设置,同时OnClickStart修改成异步方法,并且实现下回调方法OnHalf

public class UniTaskTest : MonoBehaviour
{
    public float G = 9.8f;
    public Transform prefab1; 
    public float FallTime = 1f;
    public Button StartButton;

    private void Start()
    {
        StartButton.onClick.AddListener(UniTask.UnityAction(OnClickStart));
    }

    private async UniTaskVoid OnClickStart()
    {
        UniTaskCompletionSource source = new UniTaskCompletionSource();
        FallTarget(prefab1,FallTime,OnHalf,source).Forget();
        await source.Task;
    }

    private void OnHalf()
    {
        prefab1.localScale *= 1.5f;
    }


    private async UniTaskVoid FallTarget(Transform targetTransform, float fallTime,System.Action onHalf,UniTaskCompletionSource source)
    {
        float startTime = Time.time;
        Vector3 startPos = targetTransform.position;
        float lastElapsedTime = 0.0f;
        while (Time.time - startTime < fallTime)
        {
            float elapsedTime = Mathf.Min(Time.time - startTime, fallTime);
            if (lastElapsedTime < fallTime * 0.5f && elapsedTime > FallTime * 0.5f)
            {
                onHalf?.Invoke();
                source.TrySetResult();
                //source.TrySetException(new System.Exception());  //手动失败
                //source.TrySetCanceled();  //手动取消
            }

            float fallY = 0 + 0.5f * G * elapsedTime * elapsedTime;
            targetTransform.position = Vector3.Lerp(startPos, startPos + Vector3.down * fallY, elapsedTime);
            lastElapsedTime = elapsedTime;
            await UniTask.Yield(this.GetCancellationTokenOnDestroy());
        }
    } 
} 

3.异步切换线程

当我们需要使用其他线程来完成一些Action的操作的时候,我们可以如下进行代码编写。

public class UniTaskTest : MonoBehaviour
{
    private async UniTaskVoid StandardStart()
    {
        int result = 0;
        await UniTask.RunOnThreadPool(() => { result = 1; });
        await UniTask.SwitchToMainThread();
        Debug.Log(result);
    }
} 

上面代码我们使用了SwitchToMainThread手动切换回主线程。那么我们就可以手动使用SwitchToThreadPool来切换至其他线程来执行下面的任务,然后使用UniTask.yield来直接切换回来主线程。这里编写一个文本读取的例子,代码如下

private async UniTaskVoid YieldSwitchThreadTest()
    {
        string fileName = Application.dataPath + "test.txt";
        await UniTask.SwitchToThreadPool();
        string fileContent = await File.ReadAllTextAsync(fileName);
        await UniTask.Yield(PlayerLoopTiming.Update);
        Debug.Log(fileContent);
    }

三、UniTask进阶提升(编程之路提升)

1.Unity特有事件转换为Unitask,异步可迭代器

I.点击按钮,第一次变大,第二次变小,第三次消失

使用一个异步可迭代器实现
请添加图片描述

public class UniTaskTest : MonoBehaviour
{
    public Button sphereButton; 
    
    private void Start()
    {
        CheckSphereClick(sphereButton.GetCancellationTokenOnDestroy()).Forget();
    }

    private async UniTaskVoid CheckSphereClick(CancellationToken token)
    {
        var asyncEnumerable = sphereButton.OnClickAsAsyncEnumerable();
        await asyncEnumerable.Take(3).ForEachAsync((_, index) =>
        {
            if (token.IsCancellationRequested)
            {
                return;
            }

            if (index == 0)
            {
                SphereTweenScale(1,sphereButton.transform.localScale.x, 4,token).Forget();
            }
            if (index == 1)
            {
                SphereTweenScale(1,sphereButton.transform.localScale.x, 2,token).Forget();
            }
        }, token);
        GameObject.Destroy(sphereButton.gameObject);
    }

    private async UniTaskVoid SphereTweenScale(float totalTime,float from,float to,CancellationToken token)
    {
        var trans = sphereButton.transform;
        float time = 0;
        while (time < totalTime)
        {
            time += Time.deltaTime;
            trans.localScale = (from + (time / totalTime) * (to - from)) * Vector3.one;
            await UniTask.Yield(PlayerLoopTiming.Update,token);
        }
    }
} 

II.按钮双击点击,进行计时,如果超过规定时间超时。

请添加图片描述

public class UniTaskTest : MonoBehaviour
{
    public Button button; 
    public Text text;
    private void Start()
    {
        CheckDoubleClickButton(button,button.GetCancellationTokenOnDestroy()).Forget();
    }

    private async UniTaskVoid CheckDoubleClickButton(Button button, CancellationToken token)
    {
        while (true)
        {
            var clickAsync = button.OnClickAsync(token);
            await clickAsync;
            text.text = "按钮点击了第一次";
            var secondClickAsync = button.OnClickAsync(token);
            int resultIndex = await UniTask.WhenAny( secondClickAsync,UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: token));
            if (resultIndex == 0)
            {
                text.text = "按钮点击了第二次";
            }
            else
            {
                text.text = "按钮点击超时";
            }
        }
    }
} 

III.按钮冷却

举一反三我们也能推出按钮冷却如何进行编写,不过这里需要使用可迭代器的ForEachAwaitAsync,这样编写的话,我们的代码都变得非常精简,不需要添加额外的字段来保存状态。

public class UniTaskTest : MonoBehaviour
{
    public Button button; 
    public Text text;
    private void Start()
    {
        CheckCoolClickButton(button,button.GetCancellationTokenOnDestroy()).Forget();
    }

    private async UniTaskVoid CheckCoolClickButton(Button button, CancellationToken token)
    {
        var asyncEnumerable =  button.OnClickAsAsyncEnumerable();
        await asyncEnumerable.ForEachAwaitAsync(async (_) =>
        {
            text.text = "正在进行冷却";
            await UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: token);
            text.text = "冷却完毕";
        },token);
    }
} 

2.AsyncReactiveProperty的使用

使用这个AsyncReactiveProperty用到基础类型上面,可以将每次基础类型的变化做成异步流,这样可以大大增加扩展性。
比如实现一个血条变化的功能

请添加图片描述
代码如下

 
using System;
using System.Threading;
using UnityEngine;
using Cysharp.Threading.Tasks;
using Cysharp.Threading.Tasks.Linq;
using UnityEngine.UI;
using UnityEngine.Windows;
using File = System.IO.File;
using Random = UnityEngine.Random;

public class AyyncReactivePropertySample : MonoBehaviour
{
    private AsyncReactiveProperty<int> currentHp;
    public int maxHp = 100;
    public float totalChangeTime = 1.0f;
    public Text ShowHpText;
    public Text StateText;
    public Text ChangeText;

    public Slider HpSlider;
    public Image HpBarImage;
    public Button HealButton;
    public Button HurtButton;

    private int maxHeal = 10;
    private int maxHurt = 10;
    
    private CancellationTokenSource cts = new CancellationTokenSource();
    private CancellationTokenSource linkCts;

    private void Start()
    {
        //设置AsyncReactiveProperty
        currentHp = new AsyncReactiveProperty<int>(maxHp);
        HpSlider.maxValue = maxHp;
        HpSlider.value = maxHp;
        
        currentHp.Subscribe(OnHpChange);
        CheckHpChange(currentHp).Forget();
        CheckFirstLowHp(currentHp).Forget();
        
        currentHp.BindTo(ShowHpText);
        HealButton.onClick.AddListener(OnClickHealButton);
        HurtButton.onClick.AddListener(OnClickHurtButton);
    }

    private async UniTaskVoid CheckHpChange(AsyncReactiveProperty<int> hp)
    {
        int hpValue = hp.Value;
        await hp.WithoutCurrent().ForEachAsync((_, index) =>
        {
            ChangeText.text = $"血条发生变化 第{index}次 变化{hp.Value - hpValue}";
            hpValue = hp.Value;
        },this.GetCancellationTokenOnDestroy());
    }

    private void OnClickHealButton()
    {
        ChangeHp(-Random.Range(0,maxHeal));
    }

    private void OnClickHurtButton()
    {
        ChangeHp(-Random.Range(0,maxHurt));
    }

    private void ChangeHp(int delta)
    {
        currentHp.Value = Mathf.Clamp(currentHp.Value + delta, 0, maxHp);
    }

    private async UniTaskVoid CheckFirstLowHp(AsyncReactiveProperty<int> hp)
    {
        await hp.FirstAsync((value) => value < maxHp * 0.4f, this.GetCancellationTokenOnDestroy());
        StateText.text = "首次血条低于界限,请注意!";
    }

    private async UniTaskVoid OnHpChange(int hp)
    {
        cts.Cancel();
        cts = new CancellationTokenSource();
        linkCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, this.GetCancellationTokenOnDestroy());
        await SyncSlider(hp,cts.Token);
    }

    private async UniTask SyncSlider(int hp,CancellationToken token)
    {
        var sliderValue = HpSlider.value;
        float needTime = Mathf.Abs(sliderValue - hp) / maxHp * totalChangeTime;
        float useTime = 0.0f;
        while (useTime < needTime)
        {
            useTime += Time.deltaTime;
            bool result = await UniTask.Yield(PlayerLoopTiming.Update, token).SuppressCancellationThrow();
            if (result)
            {
                return;
            }
            
            var newValue = sliderValue + (hp - sliderValue) * (useTime / needTime);
            SetNewValue(newValue);
        }
    }

    private void SetNewValue(float newValue)
    {
        if(!HpSlider) return;
        HpSlider.value = newValue;
        HpBarImage.color = newValue / maxHp < 0.4f ? Color.red  : Color.white;
    }
}

再熟悉一下下UniTask的可迭代器方面的运用,简单实现一下一个玩家控制器,使用这些代码的好处是,介绍其他字段增加代码的可读性和扩展性,代码如下


public struct ControlParam
{
    [Header("旋转速度")] public float rotateSpeed;
    [Header("移动速度")] public float moveSpeed; 
    [Header("摄像机")] public float cameraDistance;
}

public class PlayerControl
{
    public Transform playerRoot;
    private ControlParam controlParams;
    public float lastFireTime;
    public Transform cameraTransform;
    
    public PlayerControl(Transform playerRoot, ControlParam controlParams,Transform cameraTrans)
    {
        this.playerRoot = playerRoot;
        this.controlParams = controlParams;
        this.cameraTransform = cameraTrans;
    }

    private void StartCheckInput()
    {
        CheckPlayerInput().ForEachAsync((delta) =>
        {
            playerRoot.position += delta.Item1;
            var cameraToPlayer = (playerRoot.forward - cameraTransform.forward).normalized;
            cameraTransform.forward = cameraToPlayer;
            cameraTransform.position = playerRoot.position - cameraToPlayer * controlParams.cameraDistance;
            playerRoot.forward = Quaternion.AngleAxis(delta.Item2, Vector3.up) * playerRoot.forward; 
        },playerRoot.GetCancellationTokenOnDestroy()).Forget();
    } 

    private Vector3 GetInputMoveValue()
    {
        var horizontal = Input.GetAxis("Horizontal");
        var vertical = Input.GetAxis("Vertical");
        Vector3 move = (playerRoot.forward * vertical + playerRoot.right * horizontal) * (controlParams.moveSpeed * Time.deltaTime);
        return move;
    }

    private IUniTaskAsyncEnumerable<(Vector3, float)> CheckPlayerInput()
    {
        return UniTaskAsyncEnumerable.Create<(Vector3, float)>(async (writer, token) =>
        {
            await UniTask.Yield();
            while (!token.IsCancellationRequested)
            {
                await writer.YieldAsync((GetInputMoveValue(), GetInputAxisValue()));
                await UniTask.Yield();
            }
        });
    }

    private float GetInputAxisValue()
    {  
        var result = Input.GetAxis("Mouse X") * controlParams.rotateSpeed;
        return Mathf.Clamp(result, -90, 90);
    }


    public void Start()
    {
        StartCheckInput();
    }
}

总结

简单贴下await在Unitask的扩展源码

public struct ResourceRequestAwaiter : ICriticalNotifyCompletion
        {
            ResourceRequest asyncOperation;
            Action<AsyncOperation> continuationAction;

            public ResourceRequestAwaiter(ResourceRequest asyncOperation)
            {
                this.asyncOperation = asyncOperation;
                this.continuationAction = null;
            }

            public bool IsCompleted => asyncOperation.isDone;

            public UnityEngine.Object GetResult()
            {
                if (continuationAction != null)
                {
                    asyncOperation.completed -= continuationAction;
                    continuationAction = null;
                    var result = asyncOperation.asset;
                    asyncOperation = null;
                    return result;
                }
                else
                {
                    var result = asyncOperation.asset;
                    asyncOperation = null;
                    return result;
                }
            }

            public void OnCompleted(Action continuation)
            {
                UnsafeOnCompleted(continuation);
            }

            public void UnsafeOnCompleted(Action continuation)
            {
                Error.ThrowWhenContinuationIsAlreadyRegistered(continuationAction);
                continuationAction = PooledDelegate<AsyncOperation>.Create(continuation);
                asyncOperation.completed += continuationAction;
            }
        }

Unitask可以很好的解决Unity C#大部分的异步写法的问题,用同步的写法写出异步的性能,大量精简异步操作的代码,底层使用结构体0GC,性能效率也不Unity自带的协程高不少。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值