1. 程式人生 > >.NET Core 3.0中IAsyncEnumerable<T>有什麼大不了的?

.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