DC-5
365 2023-04-03 03:46:46
阅读目录(Content)
最近在看neuecc大佬写的一些库:https://neuecc.medium.com/,其中对await
,async
以及Linq查询关键字做了一些神奇的扩展,
使其可以拿来做些自定义操作,并且不需要引用System.Linq
之类的对应命名空间。
关于这些功能的实现,对此进行了学习并在Unity3D下进行测试。
对于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):
对于该接口的实现,为了方便举例;这里不考虑同步情况而是都算作异步处理
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异常捕获较为友好。
我们知道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
关键字的自定义化操作,而对于where
、skip
等操作类似,不再举例。
最后c#关键字自定义化的介绍就写到这里,至于怎么去用就仁者见仁智者见智了。