.NET最佳实践:webapi返回IAsyncEnumerable提升性能

什么是IAsyncEnumerable

IAsyncEnumerable<T> 是 .NET 中用于表示异步数据流的接口。

它允许你逐个异步地获取数据项,而不是将所有数据一次性加载到内存中。这样可以减少内存占用,尤其在处理大量数据时更加高效。

IEnumerable<T> 不同,IEnumerable<T> 是同步的,要求所有数据在返回之前就加载完成。

IAsyncEnumerable<T> 是异步的,支持在数据流被请求时逐步加载,适合处理从数据库或网络等源异步获取的数据。

示例:

public async IAsyncEnumerable<int> GetNumbersAsync() {     for (int i = 0; i < 10; i++)     {         await Task.Delay(100);  // 模拟异步操作         yield return i;     } } 

好处

减少内存占用IAsyncEnumerable<T> 逐步加载数据,避免了需要将所有数据一次性加载到内存中。对于大数据量的查询,能显著减少内存压力。

提升响应性能:在 WebAPI 中,返回 IAsyncEnumerable<T> 可以让客户端在获取部分数据时立即开始处理,而无需等待所有数据都加载完成。这使得响应时间更短,提升用户体验。

避免阻塞操作:使用 async/await 使得 WebAPI 不会被同步阻塞操作所拖慢,能够更好地处理并发请求。

以 WebAPI + EFCore 举例

假设我们需要通过 Entity Framework Core 从数据库中查询大量记录。

如果我们一次性加载所有数据,可能会导致内存占用过高,甚至影响性能。使用 IAsyncEnumerable<T>,我们可以逐个获取数据。

示例代码:

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;         }     } } 

在这个例子中,AsAsyncEnumerable() 方法将 DbSet<Product> 转换成一个异步数据流,await foreach 循环逐个从数据库中异步获取数据并返回,避免了内存占用过多。

以 WebAPI + HTTPClient 举例

在 WebAPI 中,你可能需要调用其他服务或外部 API 来获取数据。使用 IAsyncEnumerable<T> 可以使得调用返回的数据逐步加载,避免等待整个请求完成后再返回。

示例代码:

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;             }         }     } } 

在这个例子中,我们通过 HTTPClient 请求外部 API 数据,并使用 IAsyncEnumerable<string> 返回每一行数据,允许客户端逐步处理数据,而无需等待所有数据都加载完毕。

客户端怎么处理 IAsyncEnumerable<T>

客户端接收到 IAsyncEnumerable<T> 数据流后,可以使用异步迭代器 await foreach 来逐步处理数据。

这样可以在数据流逐步传输过程中及时处理和显示数据,而不必等待全部数据加载完成。

C# 客户端代码:
public class ProductClient {     private readonly HttpClient _httpClient;      public ProductClient(HttpClient httpClient)     {         _httpClient = httpClient;     }      public async Task GetProductsAsync()     {         var response = await _httpClient.GetAsync("https://example.com/products");         response.EnsureSuccessStatusCode();          await foreach (var product in response.Content.ReadFromJsonAsync<IAsyncEnumerable<Product>>())         {             Console.WriteLine(product.Name);         }     } } 
JavaScript 客户端处理:

在 JavaScript 中,客户端可以使用 fetch API 和流(Streams)来逐步处理数据。WebAPI 返回的数据流可以通过 Response.body.getReader() 来读取并逐步消费。

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);  // 逐步输出数据     } } 

通过上述方式,JavaScript 客户端可以逐步处理 WebAPI 返回的异步流,提升用户体验和响应速度。

发表评论

评论已关闭。

相关文章