澄清 IAsyncEnumerable 如何与 ASP.NET Web API 一起工作

Clarification on how IAsyncEnumerable works with ASP.NET Web API

我在 ASP.NET Web API 项目中探索 IAsyncEnumerable 时遇到了一个有趣的行为。 考虑以下代码示例:

    // Code Sample 1
    [HttpGet]
    public async IAsyncEnumerable<int> GetAsync()
    {
        for (int i = 0; i < 10; i++)
        {
            await Task.Delay(1000);
            yield return i;
        }
    }


    // Code Sample 2
    [HttpGet]
    public async IAsyncEnumerable<string> GetAsync()
    {
        for (int i = 0; i < 10; i++)
        {
            await Task.Delay(1000);
            yield return i.ToString();
        }
    }

示例 1(整数数组)returns {} 作为 JSON 结果。

示例 2 returns 预期结果 ["0","1","2","3","4","5","6","7","8","9"]。但是,整个 JSON 数组在等待 10 秒后立即返回。当 IAsyncEnumerable 接口预期的数据可用时,不应该返回它吗?或者有什么特定的方式可以使用这个网站 api 吗?

在 .NET 6 中,问题提出后大约 2 年,它按您预期的那样工作。

[HttpGet]
public async IAsyncEnumerable<int> Get()
{
    for(int i = 0; i < 10; i++)
    {
        await Task.Delay(TimeSpan.FromSeconds(1));
        yield return i;
    }
}

将导致浏览器随时间接收部分结果。

~3 秒后:

~5 秒后:

~10 秒后:


.NET6 之前

直到 .NET 6,在您提出问题时,Web api 调用不会每秒 return 部分 json。必须等待 10x1 秒的 json 序列化程序(或调用 json 序列化程序的代码,它是 ASP .NET 的一部分)。一旦框架代码和序列化程序获得所有数据,它们将被序列化并作为单个响应提供给客户端。

Controller action return types in ASP.NET Core web API中我们可以读到:

In ASP.NET Core 3.0 and later, returning IAsyncEnumerable from an action:

  • No longer results in synchronous iteration.
  • Becomes as efficient as returning IEnumerable.

ASP.NET Core 3.0 and later buffers the result of the following action before providing it to the serializer: (...)

在 ASP.NET Core 5 中,类型 IAsyncEnumerable 的实例确实已通过在内存中缓冲序列并一次性格式化缓冲集合来处理。这就解释了为什么您没有收到部分结果。

但是,使用 ASP.NET Core 6.0 这将是可能的

In ASP.NET Core 6, when formatting using System.Text.Json, MVC no longer buffers IAsyncEnumerable instances. Instead, MVC relies on the support that System.Text.Json added for these types (reference)

ASP.NET Core 6 计划于 2021 年 11 月发布 (reference)。已经可以使用预览版测试新行为。我使用预览版 6.0.100-preview.6.21355.2 成功测试了以下代码。该代码生成无限的整数流,并通过使用 IAsyncEnumerable 的控制器 returns 生成它。 while (true) 循环“证明”在处理所有内容之前返回数据,因为显然循环永远不会终止*。

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace dot_net_api_streaming.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class LoopController : ControllerBase
    {

        [HttpGet]
        public IAsyncEnumerable<int> Get()
        {
            return GetInfiniteInts();
        }

        private async IAsyncEnumerable<int> GetInfiniteInts()
        {
            int index = 0;
            while (true)
                yield return index++;
        }
    }
}
 

*在试验我的代码时请记住这一点,这样您的机器就不会崩溃:)