Unitask

1,822 阅读5分钟

Unitask API

UniTask.WaitUntil
UniTask.WaitWhile
UniTask.WaitUntilValueChanged
UniTask.SwitchToThreadPool
UniTask.SwitchToTaskPool
UniTask.SwitchToMainThread
UniTask.SwitchToSynchronizationContext
UniTask.Yield
UniTask.Run
UniTask.Lazy
UniTask.Void
UniTask.ConfigureAwait
UniTask.DelayFrame
UniTask.Delay(…, bool ignoreTimeScale = false, …) parameter


        // 等待100帧
        await UniTask.DelayFrame(100);

        // 等待10s
        await UniTask.Delay(TimeSpan.FromSeconds(10));
        
        // 等到最后一帧
        await UniTask.WaitForEndOfFrame();

        // 等待固定跟新
        await UniTask.WaitForFixedUpdate();

        // 等到isActive变为true时通过
        
        await UniTask.WaitUntil(() => isActive == true);
        
        // 等到x.isActive值变化时通过
        await UniTask.WaitUntilValueChanged(this, x => x.isActive);        

        // 可以等待协程
        //await FooCoroutineEnumerator();

        // 等待标准任务
        await UniTask.Run(() => 100);

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

        // 返回主线程(与 UniRx 中的"ObserveOnMainThread"相同)
        await UniTask.SwitchToMainThread();

        // 获取异步网络请求   using UnityEngine.Networking;
        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"));

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

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

什么是UniTask

UniTask是基于C# 5的async和await异步定制的一套Unity优化的异步解决方案;需要Unity2017以上的版本,并且需要升级c#库, 但并不如UniRx项目提供的更为轻量级,可是UniTask拥有强大的async/await和unity进行交互。

一般什么情况下使用UniTask

一般来说首先想到的是网络相关的处理和资源加载需要用UniTask来处理,使用UniRx来处理感觉不够用;还有复杂的运算也可以使用UniTask,当作一个线程来使用就好。

基于Unity AsyncOperation拓展的async&awaitAPI

所有Unity中的异步AsyncOperation都可以使用await来等待异步结果,包括AsyncLoadScene、AsyncLoadAsset、AsyncOperation

public class No22_UniTask : MonoBehaviour
{
  async void StartAsync()
  {
    // 加载资源
    var load = await Resources.LoadAsync<TextAsset>("1");
    // 加载场景
    await SceneManager.LoadSceneAsync("SampleScene")
    .ConfigureAwait(Progress.Create<float>(p => Debug.LogFormat("p:{0}", p)));
    // 网络请求
    UnityWebRequest req = UnityWebRequest.Get("[http://www.baidu.com](http://www.baidu.com)");
    var op = await req.SendWebRequest();
  }
  void Start()

  {
    StartAsync();
  }
}
Start外部定义方法
public async UniTaskVoid LoadManyAsync()
{
    //UnitaskVoid可以写成Unitask
}
Start内部定义方法
Func<UniTaskVoid> DemoAsync = async () =>
        {
            
        }; 
        DemoAsync();//只能在Start中执行方法

点击立即事件

    //相同按钮的多次点击事件
    public Button button;  
    void Start()
    {        
        Func<UniTaskVoid> DemoAsync = async () =>
        {
            //await Btn1.GetAsyncClickEventHandler().OnClickAsync();
            //await Btn1.OnClickAsync();
            //Debug.Log("点击事件");
            await TripleClick();
            Debug.Log("TripleClick被点击");
        };
        DemoAsync();
    }

    async UniTask TripleClick()
    {
        using (var handler = button.GetAsyncClickEventHandler())
        {
            await handler.OnClickAsync();
            await handler.OnClickAsync();
            await handler.OnClickAsync();
            Debug.Log("Three times clicked");
        }
    }
    
    //相同按钮的多次点击事件--使用异步 LINQ 1 
    public Button button;
    void Start()
    {        
        Func<UniTaskVoid> DemoAsync = async () =>
        {            
            await TripleClick();
            Debug.Log("TripleClick被点击");
        };
        DemoAsync();        
    }
    async UniTask TripleClick()
    {
        await button.OnClickAsAsyncEnumerable().Take(3).LastAsync() ;        
        Debug.Log("Three times clicked");
    }
    
    //相同按钮的多次点击事件--使用异步 LINQ 2      
    public Button button;       
    void Start()
    {        
        Func<UniTaskVoid> DemoAsync = async () =>
        {            
            await TripleClick();
            Debug.Log("TripleClick被点击");
        };
        DemoAsync();        
    }
    async UniTask TripleClick()
    {
        await button.OnClickAsAsyncEnumerable().Take(3).ForEachAsync(_ =>
        {
            Debug.Log("Every clicked");//每次点击执行事件
        });
        Debug.Log("Three times clicked, complete.");//点击3次结束后执行事件
    }
   
    //不同按钮的顺序点击事件    不受顺序限制可以使用WhenAll代替   
    public Button button1;
    public Button button2;
    public Button button3; 
    void Start()
    {        
        Func<UniTaskVoid> DemoAsync = async () =>
        {            
            await TripleClick();
            Debug.Log("TripleClick被点击");
        };
        DemoAsync();        
    }
    async UniTask TripleClick()
    {
        await button1.OnClickAsync();
        await button2.OnClickAsync();
        await button3.OnClickAsync();
        Debug.Log("Three times clicked");
    }

点击延时响应事件

    private bool mValue;
    public Button button1;
    public Button button2;
    public Button button3;   
    void Start()
    {        
        Func<UniTaskVoid> DemoAsync = async () =>
        {            
            await TripleClick();
            Debug.Log("TripleClick被点击");
            await button2.OnClickAsync();
            Debug.Log(2);
        };
        DemoAsync();        
    }
    async UniTask TripleClick()
    {
        //pull-type异步流在序列中的异步处理完成之前不会获得下一个值。这可能会泄漏push-type事件(如按钮)中的数据。
        //await button1.OnClickAsAsyncEnumerable().ForEachAwaitAsync(async x =>
        //{
        //    await UniTask.Delay(TimeSpan.FromSeconds(3));
        //    Debug.Log(1);
        //    //遍历每次点击的事件隔3s执行一次,不会跳出从等待,因此只会输出 1
        //});
        //使用Queue()方法,该方法还将在异步处理期间对事件进行排队。
        //await button1.OnClickAsAsyncEnumerable().Queue().ForEachAwaitAsync(async x =>
        //{
        //    await UniTask.Delay(TimeSpan.FromSeconds(3));
        //    Debug.Log(1);
        //    //遍历每次点击的事件隔3s执行一次,不会跳出从等待,因此只会输出 1
        //});
        //使用Subscribe,fire-and-forget样式。
        button1.OnClickAsAsyncEnumerable().Subscribe(async x =>
        {
            await UniTask.Delay(TimeSpan.FromSeconds(3));
            Debug.Log(1);
            //会先输出 TripleClick被点击,然后如果button2被点击一次,会输出 2
            //如果button1被点击,则输出1
        });
    }

UniTask.WhenAll

    public Button Btn1;
    public Button Btn2;
    public Button Btn3;
    void Start()
    {
        DemoAsync();
    }
    async UniTaskVoid DemoAsync()
    {
        await UniTask.WhenAll(
            GetValue1(),
            GetValue2(),
            GetValue3());
        Debug.Log("全部被点击完");
    }    
    async UniTask GetValue1()
    {
        await Btn1.OnClickAsync();
        Debug.Log(1);
    }
    async UniTask GetValue2()
    {
        await Btn2.OnClickAsync();
        Debug.Log(2);
    }
    async UniTask GetValue3()
    {
        await Btn3.OnClickAsync();
        Debug.Log(3);
    }
    async UniTaskVoid DemoAsync12()
    {
        await UniTask.WhenAll(
            GetValue1(),
            GetValue2()
        );
    } 

Unitask.WhenAny

    public Button Btn1;
    public Button Btn2;
    public Button Btn3;
    void Start()
    {
        DemoAsync();
    }
    async UniTaskVoid DemoAsync()
    {
        await UniTask.WhenAny(
            GetValue1(),
            GetValue2(),
            GetValue3());
        Debug.Log("任何一个被点击");
    }
    async UniTask GetValue1()
    {
        await Btn1.OnClickAsync();
        Debug.Log(1);
    }
    async UniTask GetValue2()
    {
        await Btn2.OnClickAsync();
        Debug.Log(2);
    }
    async UniTask GetValue3()
    {
        await Btn3.OnClickAsync();
        Debug.Log(3);
    }

    Func<UniTaskVoid> ClickAfterDuanLu = async () =>
    {
        await UniTask.WhenAny(
                   GetClick1(),
                   GetClick2(),
                   GetClick3());
    };

UniTask.WaitUntil 相当于Update每帧检测条件

    //一直等待直到达到某种条件,才执行下面的代码
    private bool mValue;
    public Button Btn;    
    void Start()
    {
        //方法1
        Func<UniTaskVoid> DemoAsync = async () =>
        {
            Debug.LogFormat("WaitUntil还未被调用");
            await Btn.OnClickAsync();
            mValue = true;                       
            await UniTask.WaitUntil(() => mValue == true);
            Debug.LogFormat("WaitUntil被调用");
        };
        //方法2
        Func<UniTaskVoid> DemoAsync = async () =>
        {
            Debug.LogFormat("WaitUntil还未被调用");        
            await UniTask.WaitUntil(() => mValue == true);
            Debug.LogFormat("WaitUntil被调用");
        };
        Btn.onClick.AddListener(() => { mValue = true; });
        
        DemoAsync();
    }
    
    await UniTask.WaitUntil(() => isAll.All(x => x));//等待全部条件变成true

UniTask.WaitWhile

    //当达到某种条件才进行等待,条件失败通过
    //mValue为true时会执行,即由false变为true时会执行
    private bool mValue;     
    void Start()
    {        
        Func<UniTaskVoid> DemoAsync = async () =>
        {
            Debug.LogFormat("WaitWhile还未被调用");
            await UniTask.Delay(TimeSpan.FromSeconds(5));           
            mValue = true;            
            await UniTask.WaitWhile(() => mValue == false);//条件不等于false时通过执行
            Debug.LogFormat("WaitWhile被调用");
        };
        DemoAsync();
    }

UniTask.WaitUntilValueChanged

一直等待直到值有变化了之后才之执行后面的代码

UniTask.SwitchToThreadPool

//之后的代码运行在线程池中
  public class No22_UniTask : MonoBehaviour
  {
    private bool mValue;
    async void StartAsync()
    {
      Debug.LogFormat("frame start:{0}", Time.frameCount);
      UniTask.DelayFrame(100).ToObservable().Subscribe(_=>mValue=true);
      //之后的代码运行中线程池中
      await UniTask.SwitchToThreadPool();
      // 当达到某种条件才进行等待,条件失败通过
      await UniTask.WaitWhile(() => mValue == false);
      Debug.LogFormat("frame end:{0}", Time.frameCount);
    }
    void Start()
    {
      StartAsync();
    }
  }

UniTask.SwitchToTaskPool

之后的代码运行在任务池中

UniTask.SwitchToMainThread

*之后的代码运行在主线程中

UniTask.SwitchToSynchronizationContext

之后的代码运行在同步句柄中

UniTask.Yield

相当于yiled return [operation]

    public class No22_UniTask : MonoBehaviour
    {
        async void StartAsync()
        {
            Debug.LogFormat("frame start:{0}", Time.frameCount);
            // PlayerLoopTiming.PostLateUpdate  =  WaitForEndOfFrame
            // yield return null
            // yield return WaitForFixedUpdate
            await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
            Debug.LogFormat("frame end:{0}", Time.frameCount);
        }
        void Start()
        {
            StartAsync();
        }
    }

UniTask.Run

启动一个标准的线程

    public class No22_UniTask : MonoBehaviour
    {
        async void StartAsync()
        {
            Debug.LogFormat("frame start:{0}", Time.frameCount);
            // 启动一个标准的线程
            await UniTask.Run(() => Debug.Log("heavy task heavy cal"));
            Debug.LogFormat("frame end:{0}", Time.frameCount);
        }
        void Start()
        {
            StartAsync();
        }
    }

UniTask.Lazy

启动一个回调线程

    public class No22_UniTask : MonoBehaviour
    {
        private UniTask<int> mLazy;
        async void StartAsync()
        {
            Debug.LogFormat("frame start:{0}", Time.frameCount);
            // 启动一个回调线程
            await UniTask.Lazy<int>(() => UniTask.DelayFrame(100));
            Debug.LogFormat("frame end:{0}", Time.frameCount);
        }
        void Start()
        {
            StartAsync();
        }
    }

UniTask.Void

void回调辅助方法

    public class No22_UniTask : MonoBehaviour
    {
        async void StartAsync()
        {
            Debug.LogFormat("frame start:{0}", Time.frameCount);
            // void回调辅助方法
            UniTask.Void(Method);
            Debug.LogFormat("frame end:{0}", Time.frameCount);
        }        
        Func<UniTaskVoid> Method = async () =>
        {
            await UniTask.DelayFrame(100);
        };
        void Start()
        {
            StartAsync();
        }
    }

UniTask.ConfigureAwait

基于AsyncOperation的拓展配置

    public class No22_UniTask : MonoBehaviour
    {
        async void StartAsync()
        {
            Debug.LogFormat("frame start:{0}", Time.frameCount);
            // 基于AsyncOperation的拓展配置
            Resources.LoadAsync<TextAsset>("1").ConfigureAwait(Progress.Create<float>(p => Debug.LogFormat("p:{0}", p)));//此句有问题
            Debug.LogFormat("frame end:{0}", Time.frameCount);
        }
        private UniTask Method()
        {
            return UniTask.DelayFrame(100);
        }
        void Start()
        {
            StartAsync();
        }
    }

UniTask.DelayFrame

延时指定的帧数执行下面的代码

UniTask.Delay(…, bool ignoreTimeScale = false, …) parameter

延时指定的时间执行下面的代码

UniTaskTracker 可视化任务

使用UniTask的监视器,可以监视当前执行的哪些任务

其他设置

Enable AutoReload:是否允许自动加载task

Enable Tracking: 是否允许跟踪task

Enable StackTrace:是否允许堆栈跟踪

Reload:重新加载task列表

GC.Collect:进行GC回收