IAsyncEnumerable如何与ASP.NET Web API一起使用的澄清

22

我在探索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(int数组)以JSON结果返回{}

示例2返回预期结果["0","1","2","3","4","5","6","7","8","9"]。然而,整个JSON数组在等待10秒后一次性返回。它不应该按照从IAsyncEnumerable接口预期的当数据变得可用时被返回吗?或者有没有特定的方式应该消耗这个Web API?


7
根据IAsyncEnumerable接口,数据一旦可用就应该返回,对吗?实际情况也确实如此,但是对于json序列化器却不是这样。 - Selvin
1
样例1....对于非引用类型似乎存在一个bug(如果你将IAsyncEnumerable<int>更改为IAsyncEnumerable<object> - 它应该可以工作,但是这样会涉及到装箱) - Selvin
@Selvin,请您详细说明一下?或者提供一段代码示例?我尝试使用C#客户端来调用这个API,结果是一样的,必须等待10秒钟... - Ravi M Patel
1
错误在这里 ... 显然就是因为这个 - Selvin
修复已在2021年4月9日的.NET 6.0中提交至PR...与此同时,在该问题的大量评论列表中有一条带有解决方法的评论 - https://github.com/dotnet/runtime/issues/1570#issuecomment-676594141 - Ruskin
显示剩余2条评论
2个回答

19
在.NET 6中,在提问大约两年后,它的工作方式与您预期的一样。
[HttpGet]
public async IAsyncEnumerable<int> Get()
{
    for(int i = 0; i < 10; i++)
    {
        await Task.Delay(TimeSpan.FromSeconds(1));
        yield return i;
    }
}

将导致浏览器逐步接收到部分结果。

大约3秒后:
enter image description here

大约5秒后:
enter image description here

大约10秒后:
enter image description here


.NET6之前

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

ASP.NET Core Web API中的Controller操作返回类型中,我们可以看到:

在ASP.NET Core 3.0及更高版本中,从操作返回IAsyncEnumerable:

  • 不再导致同步迭代。
  • 与返回IEnumerable一样有效率。

ASP.NET Core 3.0及更高版本在提供以下操作结果之前缓冲其结果:

(...)

当HTTP状态码200被发送后,在枚举过程中抛出异常会发生什么? - TimTIM Wong
有人可以告诉我浏览器使用了什么机制来实现这种行为吗?就是数字一个接一个地逐渐显示出来的那种效果。谢谢。 - Robotronx

8
在ASP.NET Core 5中,确实通过在内存中缓冲序列并一次性格式化缓冲集合来处理类型为IAsyncEnumerable的实例。这就解释了为什么您没有收到部分结果。
然而,在ASP.NET Core 6.0中,这将成为可能!
在ASP.NET Core 6中,使用System.Text.Json进行格式化时,MVC不再缓冲IAsyncEnumerable实例。相反,MVC依赖于System.Text.Json为这些类型添加的支持(参考reference)。
ASP.NET Core 6的发布计划于2021年11月(reference)实现。使用预览版本已经可以测试新功能。我成功地使用预览版本6.0.100-preview.6.21355.2测试了以下代码,该代码生成一个无限流整数,并通过控制器使用IAsyncEnumerable返回它。 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++;
        }
    }
}
 

*请记住,在尝试我的代码时要谨慎,以免您的机器崩溃 :)


@RaviMPatel,我将这个问题恢复到第一个版本,因为我认为你的贡献与原始答案有足够大的偏差,可以被视为不同的答案。你可以考虑将你的贡献(第3版)发布为一个新答案,以便它可以独立地进行评估(投票)。 - Theodor Zoulias
如果在迭代过程中发生错误,HttpStatus将如何处理?早期答案的评论中发布了一个问题。 - Arjun Menon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接