C#.NET 拾遺補(bǔ)漏之理解 C# 中的異步流
本文轉(zhuǎn)載自微信公眾號(hào)「精致碼農(nóng)」,作者liamwang 。轉(zhuǎn)載本文請(qǐng)聯(lián)系精致碼農(nóng)公眾號(hào)。
在閱讀本文前,建議先閱讀本系列的上一篇文章『理解 yield 關(guān)鍵字』。我們知道,使用 C# 的 yield 關(guān)鍵字可以實(shí)現(xiàn)一個(gè)迭代器(Iterator),使用 async/await 關(guān)鍵字可以實(shí)現(xiàn)一個(gè)異步方法。異步流(Asynchronous Stream)就是這兩種功能的結(jié)合體,它實(shí)現(xiàn)了以異步的方式生成和消費(fèi)一組數(shù)據(jù)系列的迭代器。
異步流的支持主要建立在 C# 8 引入的兩個(gè)接口上:
- public interface IAsyncEnumerable<out T>
- {
- IAsyncEnumerator<T> GetAsyncEnumerator (...);
- }
- public interface IAsyncEnumerator<out T>: IAsyncDisposable
- {
- T Current { get; }
- ValueTask<bool> MoveNextAsync();
- }
所以理解了上一篇我們講的 yield 關(guān)鍵字,就很容易理解異步流,它只是在原來的基礎(chǔ)上支持通過 yield return 返回異步得到的一系列結(jié)果值而已。從序列中獲取每個(gè)元素的行為(MoveNextAsync)是一個(gè)異步操作,元素是以零散的方式到達(dá),這就形成了所謂的“異步流”(比如視頻流中的數(shù)據(jù))。
這里 IAsyncEnumerator 接口的 ValueTask
在上一篇文章中的 Fibonacci 方法中,Thread.Sleep(1000) 用來模擬一個(gè)耗時(shí)操作,它是“同步”的:
- IEnumerable<int> Fibonacci(int count)
- {
- int prev = 1;
- int curr = 1;
- for (int i = 0; i < count; i++)
- {
- yield return prev;
- Thread.Sleep(1000);
- int temp = prev + curr;
- prev = curr;
- curr = temp;
- }
- }
為了提高程序執(zhí)行效率,我們很有可能需要把 Thread.Sleep(1000) 改成“異步”的。如果使它生成異步的數(shù)據(jù)流,該怎么做呢?這就需要同時(shí)用到迭代器和異步方法了,也就是說方法中要同時(shí)使用 yield return 和 async/await 關(guān)鍵字。要支持這一特性,就要使用 IAsyncEnumerable
- async IAsyncEnumerable<int> FibonacciAsync(int count)
- {
- int prev = 1;
- int curr = 1;
- Random r = new();
- for (int i = 0; i < count; i++)
- {
- yield return prev;
- await Task.Delay(1000);
- int temp = prev + curr;
- prev = curr;
- curr = temp;
- }
- }
不同的是,這個(gè)方法允許調(diào)用者以異步的方式消費(fèi)它生成的數(shù)字系列,換句話說就是使用 await foreach 來遍歷消費(fèi)這個(gè)方法的返回結(jié)果(IAsyncEnumerable
- await foreach (var n in FibonacciAsync(10))
- Console.Write("{0} ", n);
但,如果要在 LINQ 查詢語句中消費(fèi)異步流,需要先引入 System.Linq.Async NuGet 包,除此之外,使用方式?jīng)]有差別:
- IAsyncEnumerable<int> query =
- from i in FibonacciAsync(10)
- where i % 2 == 0
- select i * 10;
- await foreach (var number in query)
- Console.WriteLine(number);
另外,在 ASP.NET Core 的 Action 方法中也支持返回異步流,如下面示例:
- [HttpGet]
- public async IAsyncEnumerable<string> Get()
- {
- using var dbContext = new BookContext();
- await foreach (var title in dbContext.Books
- .Select(b => b.Title)
- .AsAsyncEnumerable())
- yield return title;
- }
綜上,可以看到,異步流解決了零散數(shù)據(jù)系列的異步生成和消費(fèi)問題。要知道,在 C# 還沒有異步流時(shí),一組數(shù)據(jù)系列(IEnumerable
參考:
- 《C# 9.0 in a nutshell》
- https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/generate-consume-asynchronous-stream