.NET Core 3.0中IAsyncEnumerable<T>有什麼大不了的?
.NET Core 3.0和C# 8.0最激動人心的特性之一就是IAsyncEnumerable<T>(也就是async流)。但它有什麼特別之處呢?我們現在可以用它做哪些以前不可能做到的事?
在本文中,我們將瞭解IAsyncEnumerable<T>要解決哪些挑戰,如何在我們自己的應用程式中實現它,以及為什麼IAsyncEnumerable<T>將在很多情況下取代Task<IEnumerable<T>>。
也許最好的證明IAsyncEnumerable < T >有用的方式是看看在沒有它的時候所面臨的的困難。
比如首先有這樣一段程式碼,按頁面獲取所有喜歡的帖子:
public async Task<IEnumerable<Post>> GetPagePostsFromLikes(int pageNumber) { // 實現省略 }
然後有另一段程式碼呼叫上面這段程式碼:
public async Task<IEnumerable<Post>> GetAllPostsFromLikes() { var allPosts = new List<Post>(); for (int page = 0; ; page++) { var posts = await GetPagePostsFromLikes(page); if (!posts.Any()) { return allPosts; } allPosts.AddRange(posts); } }
注意,上面這個方法有一個問題,我們對每個頁面的結果進行迴圈並放入List<Post>中,最後返回整個結果。假設有上億個page頁面的的帖子,那麼所有這上億個page頁面的帖子都需要在返回值之前被載入。顯然是非常低效的。
也許我們可以不使用Task來替換上面的方法:
public IEnumerable<Post> GetAllPostsFromLikes() { for (int page = 0; ; page++) { var posts = GetPagePostsFromLikes(page).GetAwaiter().GetResult(); if (!posts.Any()) { yield break; } foreach (var post in posts) { yield return post; } } }
在上面程式碼中,返回IEnumerable<T>的方法可以使用yield return語句將每個資料片段返回給呼叫者。
但是,請勿這樣做! 上面的程式碼意味著如果我們從非同步方法中呼叫第三個函式,執行緒池將持續迭代返回的IEnumerable,直到其完成,也就是說當有足夠多的併發訪問同一個執行緒,勢必會造成阻塞。
如果我們可以用非同步方法來使用yield return就好了!可惜那是不可能的……直到現在。
這個時候IAsyncEnumerable<T> 就該出場啦!!!!!!
IAsyncEnumerable<T>是在.NET Core 3 (.NET Standard 2.1)引入的。它公開了一個列舉器,該列舉器具有可以等待的MoveNextAsync()方法。這意味著生產者可以在產生結果之間進行非同步呼叫。
與返回任務<IEnumerable<T>>不同,我們的方法現在可以返回IAsyncEnumerable<T>,並使用yield return來發送資料:
public async IAsyncEnumerable<Post> GetAllPostsFromLikes() { for (int page = 0; ; page++) { var posts = GetPagePostsFromLikes(page).GetAwaiter().GetResult(); if (!posts.Any()) { yield break; } foreach (var post in posts) { yield return post; } } }
為了使用結果,我們需要使用c# 8中新的await foreach()語法:
await foreach (var post in postsRepository.GetAllPostsFromLikes()) { Console.WriteLine(post); }
這個好多了。該方法生成可用的資料。呼叫程式碼以自己的節奏使用資料。
從.NET Core 3.0 Preview 7 開始,ASP.NET就能夠從API控制器動作中返回IAsyncEnumerable<T>,這意味著我們可以直接返回方法的結果——有效地將資料從資料庫流到HTTP響應。
[HttpGet] public IAsyncEnumerable<Post> Get() => postsRepository.GetAllPostsFromLikes();
隨著時間的推移,隨著.NET Core3.0和.NET Standard2.1的發展,我們將會看到IAsyncEnumerable<T>被用在我們通常使用Task<IEnumerable<T>>的地方。
總結
IAsyncEnumerable<T>是. net的一個很受歡迎的新特性,在很多情況下,它可以使程式碼更簡潔、更高效。
想要了解更多,請參考以下資源:
- Tutorial: Generate and consume async streams using C# 8.0 and .NET Core 3.0
- C# language proposals - Async Streams
- new features in .NET Core 3