什么是IAsyncEnumerable
IAsyncEnumerable<T> 是 .NET 中用于表示異步數(shù)據(jù)流的接口。
它允許你逐個(gè)異步地獲取數(shù)據(jù)項(xiàng),而不是將所有數(shù)據(jù)一次性加載到內(nèi)存中。這樣可以減少內(nèi)存占用,尤其在處理大量數(shù)據(jù)時(shí)更加高效。
與 IEnumerable<T> 不同,IEnumerable<T> 是同步的,要求所有數(shù)據(jù)在返回之前就加載完成。
而 IAsyncEnumerable<T> 是異步的,支持在數(shù)據(jù)流被請求時(shí)逐步加載,適合處理從數(shù)據(jù)庫或網(wǎng)絡(luò)等源異步獲取的數(shù)據(jù)。
示例:
public async IAsyncEnumerable<int> GetNumbersAsync()
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(100); // 模擬異步操作
yield return i;
}
}
好處
減少內(nèi)存占用:IAsyncEnumerable<T> 逐步加載數(shù)據(jù),避免了需要將所有數(shù)據(jù)一次性加載到內(nèi)存中。對于大數(shù)據(jù)量的查詢,能顯著減少內(nèi)存壓力。
提升響應(yīng)性能:在 WebAPI 中,返回 IAsyncEnumerable<T> 可以讓客戶端在獲取部分?jǐn)?shù)據(jù)時(shí)立即開始處理,而無需等待所有數(shù)據(jù)都加載完成。這使得響應(yīng)時(shí)間更短,提升用戶體驗(yàn)。
避免阻塞操作:使用 async/await 使得 WebAPI 不會被同步阻塞操作所拖慢,能夠更好地處理并發(fā)請求。
以 WebAPI + EFCore 舉例
假設(shè)我們需要通過 Entity Framework Core 從數(shù)據(jù)庫中查詢大量記錄。
如果我們一次性加載所有數(shù)據(jù),可能會導(dǎo)致內(nèi)存占用過高,甚至影響性能。使用 IAsyncEnumerable<T>,我們可以逐個(gè)獲取數(shù)據(jù)。
示例代碼:
public class ProductController : ControllerBase
{
private readonly ApplicationDbContext _context;
public ProductController(ApplicationDbContext context)
{
_context = context;
}
[HttpGet("products")]
public async IAsyncEnumerable<Product> GetProductsAsync()
{
await foreach (var product in _context.Products.AsAsyncEnumerable())
{
yield return product;
}
}
}
在這個(gè)例子中,AsAsyncEnumerable() 方法將 DbSet<Product> 轉(zhuǎn)換成一個(gè)異步數(shù)據(jù)流,await foreach 循環(huán)逐個(gè)從數(shù)據(jù)庫中異步獲取數(shù)據(jù)并返回,避免了內(nèi)存占用過多。
以 WebAPI + HTTPClient 舉例
在 WebAPI 中,你可能需要調(diào)用其他服務(wù)或外部 API 來獲取數(shù)據(jù)。使用 IAsyncEnumerable<T> 可以使得調(diào)用返回的數(shù)據(jù)逐步加載,避免等待整個(gè)請求完成后再返回。
示例代碼:
public class ExternalApiController : ControllerBase
{
private readonly HttpClient _httpClient;
public ExternalApiController(HttpClient httpClient)
{
_httpClient = httpClient;
}
[HttpGet("external-data")]
public async IAsyncEnumerable<string> GetExternalDataAsync()
{
var response = await _httpClient.GetAsync("https://api.example.com/data");
response.EnsureSuccessStatusCode();
var stream = await response.Content.ReadAsStreamAsync();
using (var reader = new StreamReader(stream))
{
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
yield return line;
}
}
}
}
在這個(gè)例子中,我們通過 HTTPClient 請求外部 API 數(shù)據(jù),并使用 IAsyncEnumerable<string> 返回每一行數(shù)據(jù),允許客戶端逐步處理數(shù)據(jù),而無需等待所有數(shù)據(jù)都加載完畢。
需使用System.Text.Json格式化程序。 使用Newtonsoft.Json或XML-based格式化程序時(shí),會緩沖結(jié)果,造成最后一次返回。
客戶端怎么處理 IAsyncEnumerable<T>
客戶端接收到 IAsyncEnumerable<T> 數(shù)據(jù)流后,可以使用異步迭代器 await foreach 來逐步處理數(shù)據(jù)。
這樣可以在數(shù)據(jù)流逐步傳輸過程中及時(shí)處理和顯示數(shù)據(jù),而不必等待全部數(shù)據(jù)加載完成。
C# 客戶端代碼:
public class ProductClient
{
private readonly HttpClient _httpClient;
public ProductClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task GetProductsAsync()
{
var products = _httpClient.GetFromJsonAsAsyncEnumerable("https://api.example.com/data");
await foreach (var p in products)
{
Console.WriteLine(DateTime.Now.ToString() + " " + p.Id);
}
}
}
JavaScript 客戶端處理:
在 JavaScript 中,客戶端可以使用 fetch API 和流(Streams)來逐步處理數(shù)據(jù)。WebAPI 返回的數(shù)據(jù)流可以通過 Response.body.getReader() 來讀取并逐步消費(fèi)。
async function fetchProducts() {
const response = await fetch('https://example.com/products');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let done = false;
let value = '';
while (!done) {
const { done: chunkDone, value: chunk } = await reader.read();
done = chunkDone;
value += decoder.decode(chunk, { stream: true });
console.log(value); // 逐步輸出數(shù)據(jù)
}
}
通過上述方式,JavaScript 客戶端可以逐步處理 WebAPI 返回的異步流,提升用戶體驗(yàn)和響應(yīng)速度。
浙公網(wǎng)安備 33010602011771號