異步編程已經流行很多年了,.NET 引入的 async 和 await 關鍵詞讓異步編程更具有可讀性,但有一個遺憾,在 C# 8 之前都不能使用異步的方式處理數據流,直到 C# 8 引入的 IAsyncEnumerable<T>
才解決了這個問題。
說到 IAsyncEnumerable<T>
,得先說一說 IEnumerable<T>
,大家都知道,它是用同步的方式來迭代 collection 集合的,而這里的 IAsyncEnumerable<T>
則是用異步方式,換句話說: IAsyncEnumerable<T>
在迭代集合的過程中不會阻塞調用線程。
IAsyncDisposable, IAsyncEnumerable<T>, IAsyncEnumerator<T>
異步迭代器
允許我們可以用異步的方式處理數據,在這之前要了解下面三個接口:IAsyncDisposable, IAsyncEnumerable<T> 和 IAsyncEnumerator<T>
,他們都是在 .NET Standard 2.1 中被引入,下面的代碼片段展示了這三個接口的定義。
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}
public interface IAsyncEnumerable<out T>
{
IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken
token = default);
}
public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
ValueTask<bool> MoveNextAsync();
T Current { get; }
}
為什么要使用異步迭代器
可以想象一下你有一個數據訪問層需要從數據庫中一次性讀取所有的數據,要想使用這個功能很簡單,可以直接調用 底層提供的異步方法 XXXAsyc
實現異步調用並且一次性返回所有數據。
只要不是將所有數據都呈現在頁面上的話,這種解決方案問題不是太大,很多時候更多的是通過 分頁讀取
的形式,其實在這方面還有一個比較好的做法就是在數據可用時立即返回給調用者。
准確的說,這里可使用 異步迭代器 的方式來解決,如果你的方法是同步返回的話,你可以使用 return yield
+ 返回值 IEnumerable<T>
模式,很遺憾的是,這種方式沒有擴展性,因為它是需要阻塞調用線程的。
最好的解決方案就是 return yield
+ 返回值 IAsyncEnumerable<T>
模式,異步迭代器方法返回的是 IAsyncEnumerable<T>
實例,並且可以包含一個或多個 yield return
語句。
在 C#8 中創建異步迭代器
下面的代碼片段展示了一個返回 Task<IEnumerable<T>>
類型的異步方法,如下代碼所示:
class Program
{
const int DELAY = 1000;
const int MIN = 1;
const int MAX = 10;
public static async Task Main(string[] args)
{
foreach (int number in await GetData())
{
Console.WriteLine($"{DateTime.Now}: number={number}");
}
Console.ReadLine();
}
public static async Task<IEnumerable<int>> GetData()
{
List<int> integers = new List<int>();
for (int i = MIN; i <= MAX; i++)
{
await Task.Delay(DELAY);
integers.Add(i);
}
return integers;
}
}
當運行上面的應用程序,它會等待 10s 之后再將所有的 1-10 的數字輸出控制台上,雖然這個 GetData
是異步的,但最終還是一次性輸出了,而不是一個一個的隔秒輸出。
這個時候可以讓 yield 關鍵詞介入,它是在 C# 2.0 中被引入的,常用於執行狀態迭代
並且按一個一個的從集合中返回數據,你不需要像上面一樣創建一個集合(integers) 再返回上去,下面的代碼片段是修改 GetData 方法並且合並了 yield 關鍵詞的版本,代碼如下:
static async IAsyncEnumerable<int> GetData()
{
for (int i = MIN; i < MAX; i++)
{
yield return i;
await Task.Delay(DELAY);
}
}
C#8 中使用異步迭代器
要想使用異步流
, 需要在 foreach 前增加一個 await 關鍵詞,如下代碼所示:
public static async Task Main(string[] args)
{
await foreach (int number in GetData())
{
Console.WriteLine($"{DateTime.Now}: number={number}");
}
Console.ReadLine();
}
下面是完整的僅供參考的代碼。
class Program
{
const int DELAY = 1000;
const int MIN = 1;
const int MAX = 10;
public static async Task Main(string[] args)
{
await foreach (int number in GetData())
{
Console.WriteLine($"{DateTime.Now}: number={number}");
}
Console.ReadLine();
}
static async IAsyncEnumerable<int> GetData()
{
for (int i = MIN; i < MAX; i++)
{
yield return i;
await Task.Delay(DELAY);
}
}
}
C# 8 中一個非常重要的特性就是支持了 IAsyncEnumerable<T>
,它可以讓你應用程序代碼更干凈,更高效 和 更高性能。
更多精彩,歡迎訂閱 👇👇👇
譯文鏈接:https://www.infoworld.com/article/3531251/how-to-use-asynchronous-streams-in-csharp-80.html