zoukankan      html  css  js  c++  java
  • 关于Linq查询关键字及await,async异步关键字的自定义扩展

    最近在看neuecc大佬写的一些库:https://neuecc.medium.com/,其中对await,async以及Linq查询关键字做了一些神奇的扩展,

    使其可以拿来做些自定义操作,并且不需要引用System.Linq之类的对应命名空间。

    关于这些功能的实现,对此进行了学习并在Unity3D下进行测试。

    1.await,async关键字的自定义扩展

    对于await关键字的自定义扩展,只需要实现GetAwaiter公共方法即可,通过扩展方法实现也可以:

    public static CoroutineAwaiter<WaitForSeconds> GetAwaiter(this WaitForSeconds instruction)
    {
        CoroutineAwaiter<WaitForSeconds> awaiter = new CoroutineAwaiter<WaitForSeconds>(instruction);
        return awaiter;
    }

    遇到await关键字时,实际会去执行GetAwaiter部分的内容。

    而如上的扩展方法是通过await实现Unity中的协程WaitForSeconds的异步封装。

    上面的方法还会看到一个返回类型,c#编译器会关注返回的类型是否实现INotifyCompletion接口

    (或实现ICriticalNotifyCompletion接口)

    注:此处代码参考Unity3dAsyncAwaitUtil(https://github.com/modesttree/Unity3dAsyncAwaitUtil)

    对于返回类型,CoroutineAwaiter<WaitForSeconds>其实现如下:

    public class CoroutineAwaiter<T> : INotifyCompletion
        where T : YieldInstruction
    {
        private T mValue;
        private Action mOnCompleted;
    
        public bool IsCompleted => false;
    
    
        public CoroutineAwaiter(T value)
        {
            mValue = value;
        }
    
        public T GetResult() => default;
    
        private IEnumerator CoroutineExec()
        {
            yield return mValue;
            mOnCompleted();
        }
    
        #region INotifyCompletion
        void INotifyCompletion.OnCompleted(Action onCompleted)
        {
            mOnCompleted = onCompleted;
    
            CoroutineRunner.Instance.StartCoroutine(CoroutineExec());
        }
        #endregion
    }

    那么返回的INotifyCompletion接口对象,c#会做如下操作,参考知乎(https://zhuanlan.zhihu.com/p/121792448):

    1. 先调用t.GetAwaiter()方法,取得等待器a
    2. 调用a.IsCompleted取得布尔类型b
    3. 如果b=true,则立即执行a.GetResult(),取得运行结果;
    4. 如果b=false,则看情况:
      1. 如果a没实现ICriticalNotifyCompletion,则执行(a as INotifyCompletion).OnCompleted(action)
      2. 如果a实现了ICriticalNotifyCompletion,则执行(a as ICriticalNotifyCompletion).OnCompleted(action)
      3. 执行随后暂停,OnCompleted完成后重新回到状态机;

    对于该接口的实现,为了方便举例;这里不考虑同步情况而是都算作异步处理

    private IEnumerator CoroutineExec()
    {
        yield return mValue;
        mOnCompleted();
    }
    
    #region INotifyCompletion
    void INotifyCompletion.OnCompleted(Action onCompleted)
    {
        mOnCompleted = onCompleted;
    
        CoroutineRunner.Instance.StartCoroutine(CoroutineExec());
    }
    #endregion

    所以OnCompleted中,通过CoroutineRunner开启一个协程,并在协程执行完后调用mOnCompleted,通知c#的异步可以继续往下执行了。

    此处代码经过测试,全部是主线程回调函数实现的等待,并不会导致线程堵塞或是开在新线程上去执行。

    CoroutineRunner实现简单的全局协程托管,该类仅测试用:

    using UnityEngine;
    
    public class CoroutineRunner : MonoBehaviour
    {
        private static CoroutineRunner sInstance;
        public static CoroutineRunner Instance => sInstance;
    
    
        private void Awake()
        {
            sInstance = this;
        }
    }
    View Code

    最终使用代码如下:

    public class Test1 : MonoBehaviour
    {
        public void Start()
        {
            _ = WaitForSecondsExecTest();
            //绕过警告提示
        }
    
        async Task WaitForSecondsExecTest()
        {
            Debug.Log("Waiting 1 second...");
            await new WaitForSeconds(1f);
            Debug.Log("Done!");
        }
    }

    这段代码运行在unity主线程上, 并通过协程控制异步逻辑执行。

    2.Linq关键字的自定义扩展

    我们知道Linq可以写出类似SQL风格的语句:

    int[] arr = new[] {1, 2, 3};
    var r = from item in arr
        where item > 0
        orderby item descending
        select item;

    而c#开源库UniRx拿这些关键字做了一些非集合查询的自定义操作:

    // composing asynchronous sequence with LINQ query expressions
    var query = from google in ObservableWWW.Get("http://google.com/")
                from bing in ObservableWWW.Get("http://bing.com/")
                from unknown in ObservableWWW.Get(google + bing)
                select new { google, bing, unknown };
    
    var cancel = query.Subscribe(x => Debug.Log(x));
    
    // Call Dispose is cancel.
    cancel.Dispose();

     (该段代码位于Sample01_ObservableWWW.cs中, UniRx地址:https://github.com/neuecc/UniRx)

    这么神奇,那么是怎么实现的呢?

    研究了下它的代码,发现实现这样的操作和GetAwaiter类似,只需包含与默认名称一致的公共方法即可。

    但是后来又发现,类型还必须包含一个泛型,C#编译器才可以成功识别:

    public class Test : MonoBehaviour
    {
        public class Result<T>//此处需有一个泛型才行
        {
            public int Select<TOut>(Func<T, TOut> selector)
            {
                return 12;
            }
        }
    
        private void Start()
        {
            Result<int> r = new Result<int>();
    
            var rInt = from item in r
                select new {item};
    
            Debug.Log("rInt: " + rInt);
            //return 12.
        }
    }

    这样就实现了select关键字的自定义化操作,而对于where、skip等操作类似,不再举例。

    最后c#关键字自定义化的介绍就写到这里,至于怎么去用就仁者见仁智者见智了

    这种写法最大的好处是不会引入System.Linq或是System.Threading等命名空间,

    但如果要和多线程的异步混用或者用Task.WaitAll之类的操作,则避免不了;这种情况还需要甚用。

  • 相关阅读:
    MySql面试题、知识汇总、牛客网SQL专题练习
    产生过拟合的原因
    《人类简史》这本烧脑书风靡全球的秘密是什么?
    厌食?暴食?试试这个 VR 新疗法
    协程、异步IO
    进程池
    进程(同步)锁
    特朗普变脸:同媒体“友好会谈”,怨媒体“死不悔改”
    多进程Queue
    redis 在 php 中的应用(事务 [ Transaction ] 篇)
  • 原文地址:https://www.cnblogs.com/hont/p/15666596.html
Copyright © 2011-2022 走看看