關於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主線程上, 並通過協程控制異步邏輯執行。

因為迭代器代碼並不直接暴露,因此對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 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關鍵字的自定義化操作,而對於whereskip等操作類似,不再舉例。

 

 

最后c#關鍵字自定義化的介紹就寫到這里,至於怎么去用就仁者見仁智者見智了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM