关于Linq查询关键字及await,async异步关键字的自定义扩展

关于Linq查询关键字及await,async异步关键字的自定义扩展

阅读目录(Content)

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

最近在看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 INotifyCompletionvoid 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主线程上, 并通过协程控制异步逻辑执行。

因为迭代器代码并不直接暴露,因此对try catch异常捕获较为友好。

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 expressionsvar 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关键字的自定义化操作,而对于whereskip等操作类似,不再举例。

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

免责声明:本网信息来自于互联网,目的在于传递更多信息,并不代表本网赞同其观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,并请自行核实相关内容。本站不承担此类作品侵权行为的直接责任及连带责任。如若本网有任何内容侵犯您的权益,请及时联系我们,本站将会在24小时内处理完毕。
相关文章
返回顶部