C#8.0: 在 LINQ 中支持异步的 IAsyncEnumerableT接口
C# 8.0中,提供了一種新的IAsyncEnumerable<T>接口,在對集合進行迭代時,支持異步操作。比如在讀取文本中的多行字符串時,如果讀取每行字符串的時候使用同步方法,那么會導致線程堵塞。IAsyncEnumerable<T>可以解決這種情況,在迭代的時候支持使用異步方法。也就是說,之前我們使用foreach來對IEnumerable進行迭代,現在可以使用await foreach來對IAsyncEnumerable<T>進行迭代,每個項都是可等待的。這種新的接口稱為async-streams發布。我們來看一下如何在LINQ中實現異步的迭代。
使用常規的IEnumerable<T>
首先我們創建一個新的Console項目,基于.NET Core 3:
這是一個很簡單的Console程序,實現了一個簡單的返回類型為IEnumerable<string>的ReadAllLines(string file)方法,從文本文件中逐行讀取文本,并逐行輸出。如果文本內容較少的話,沒什么問題。但如果我們使用過aync/await,就會了解,在IO操作如讀取或寫入文件的時候,最好使用異步方法以避免線程阻塞。讓我們來改進一下。
使用異步的IAsyncEnumerable<T>
可以優化的是下面這句:
對于IO操作,最好使用異步方式。這里可使用相應的異步方法:
我們說“異步是傳染的”,如果這里使用異步,那么相應的該方法的返回值也要使用異步,所以需要將返回值改為static async Task<IEnumerable<string>>,但這樣會得到一個錯誤:
因為Task<IEnumerable<string>>并不是一個可以迭代的接口類型,所以我們無法在方法內部使用yield關鍵字。解決問題的辦法是使用新的IAsyncEnumerable接口:
按F12查看該接口的定義:
這是一個異步的迭代器,并提供了CancellationToken。再按F12查看IAsyncEnumerator<T>的定義,可發現里面是這樣的:
這里MoveNextAsync()方法實際是返回了一個結果類型為bool的Task,每次迭代都是可等待的,從而實現了迭代器的異步。
使用await foreach消費IAsyncEnumerable<T>
當我們做了以上改動之后,ReadAllLines()方法返回的是一個支持異步的IAsyncEnumerable,那么在使用的時候,也不能簡單的使用foreach了。修改Main方法如下:
首先在foreach之前添加await關鍵字,還要需要將Main方法由void改為async Task。這樣整個程序都是異步執行了,不會再導致堵塞了。這個例子只是一個簡單的demo,是否使用異步并不會感覺到明顯的區別。如果在迭代內部需要比較重的操作,如從網絡獲取大量數據或讀取大量磁盤文件,異步的優勢還是會比較明顯的。
使用LINQ消費IAsyncEnumerable<T>
使用LINQ來操作集合是常用的功能。如果使用IEnumberable,在Main方法中可以做如下改動:
或:
如果使用了新的IAsyncEnumerable,你會發現無法使用Where等操作符了:
目前LINQ還沒有提供對IAsyncEnumerable的原生支持,不過微軟提供了一個Nuget包來實現此功能。在項目中打開Nuget Package Manger搜索安裝System.Linq.Async,注意該包目前還是預覽版,所以要勾選include prerelease才能看到。安裝該Nuget包后,Linq查詢語句中的錯誤就消失了。
在System.Linq.Async這個包中,對每個同步的LINQ方法都做了相應的擴展。所以基本上代碼無需什么改動即可正常編譯。
對于LINQ中的條件語句,也可以使用WhereAwait()方法來支持await:
如需要在條件語句中進行IO或網絡請求等異步操作,可以這樣用:
DoSomeHeavyOperationsAsync方法的簽名如下:
小結
通過以上的示例,我們簡要了解了如何使用IAsyncEnumerable接口以及如何在LINQ中實現異步查詢。在使用該接口時,我們需要創建一個自定義方法返回IAsyncEnumerable<T>來代替IEnumberable<T>,這個方法可稱為async-iterator方法,需要注意以下幾點:
該方法應該被聲明為async。
返回IAsyncEnumerable<T>。
同時使用await及yield。如await foreach,yield return或yield break等。
例如:
此外還有一些限制:
無法在try的finally塊中使用任何形式的yield語句。
無法在包含任何catch語句的try語句中使用yield return語句。
期待.NET Core 3的正式發布!
點個贊再走唄?
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的C#8.0: 在 LINQ 中支持异步的 IAsyncEnumerableT接口的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小白开学Asp.Net Core 《六》
- 下一篇: 解读 Microsoft.NET.Sdk