16

在 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"]。但是,等待 10 秒后会立即返回整个 JSON 数组。当数据从 IAsyncEnumerable 接口按预期变为可用时,不应该返回它吗?或者有什么特定的方式应该使用这个 web api?

4

2 回答 2

8

在 .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 调用不会每秒返回部分 json。json 序列化器必须等待 10x1 秒(或调用 json 序列化器的代码,它是 ASP .NET 的一部分)。一旦框架代码和序列化器获得所有数据,它将被序列化并作为单个响应提供给客户端。

ASP.NET Core Web API 中的控制器操作返回类型中,我们可以阅读:

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

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

ASP.NET Core 3.0 及更高版本在将以下操作的结果提供给序列化程序之前对其进行缓冲:(...)

于 2019-11-15T13:02:16.363 回答
6

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

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

在 ASP.NET Core 6 中,使用 System.Text.Json 进行格式化时,MVC 不再缓冲 IAsyncEnumerable 实例。相反,MVC 依赖于 System.Text.Json 为这些类型添加的支持(参考

ASP.NET Core 6 计划于 2021 年 11 月发布(参考)。已经可以使用预览版测试新行为。我使用预览版 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++;
        }
    }
}
 

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

于 2021-08-13T06:57:31.843 回答