我可以(或应该)在 Web API 控制器中使用 IAsyncEnumerable<T> 而不是 Task<ActionResult<IEnumerable<T>>>

Bel*_*way 4 entity-framework-core asp.net-core-webapi iasyncenumerable

我目前有一个 Web API

  • FromSqlRaw(...).ToListAsync()在存储库中使用获取一行数据
  • Ok(data.ToArray())Task<ActionResult<IEnumerable<MyClass>>>通过控制器一样返回此数据。

现在我想知道我是否应该或可以使用 IAsyncEnumerable 作为返回类型。这个想法是在存储库和控制器中使用它。然而,在这个(现在已经破旧的)线程中,它声明不应该使用它。这里建议的解决方案类似于:

FromSqlRaw(...).AsNoTracking().AsAsyncEnumerable()
Run Code Online (Sandbox Code Playgroud)

至于控制器,我希望将响应包装起来ActionResult以显式设置返回码。然而,目前似乎并没有工作。

我应该只为存储库应用解决方案并将结果作为列表在我的控制器中使用还是保持原样?

Pet*_*ala 6

IAsyncEnumerable给你的接口基于拉的异步数据检索。换句话说,此 API 表示一个迭代器,在该迭代器中异步获取下一项。

这意味着您将分几轮接收数据,并且每轮都以异步方式接收。

  • 之前IAsyncEnumerable你可以使用IEnumerable<Task<T>>,它代表一堆具有返回类型的异步操作T

  • Task<IEnumerable<T>>表示具有返回类型的单个异步操作IEnumerable<T>


让我们将这些知识应用到 WebAPI 中:

  • 从 HTTP 消费者的角度来看,Task<ActionResult<T>>和 之间没有区别ActionResult<T>。从用户的角度来看,这是一个实现细节。
  • WebAPI 控制器的操作实现了请求-响应模型。这意味着在消费者端发送单个请求并接收单个响应。
  • 如果消费者再次调用相同的操作,则将实例化一个新控制器并处理该请求。

这意味着 API 的使用者无法利用IAsyncEnumerable它作为操作结果类型公开的优势。


Mon*_*rso 6

在 .net 6 中,使用System.Text.Json时, MVC 的IAsyncEnumerable处理发生了变化

MVC 不再缓冲 IAsyncEnumerable 实例。相反,MVC 依赖于 System.Text.Json 为这些类型添加的支持。

这意味着控制器将立即开始发送输出,并且客户端可以在收到响应块时开始处理它。

这是一个借助新的最小 API的示例:

端点绑定:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// this endpoint return IAsyncEnumerable<TestData>
app.MapGet("/asyncEnumerable/{count}", (int count) => GetLotsOfDataAsyncEnumerable(count));

// and this one returns Task<IEnumerable<TestData>>
app.MapGet("/{count}", async (int count) => await GetLotsOfDataAsync(count));
app.Run();
Run Code Online (Sandbox Code Playgroud)

控制器方法:

async Task<IEnumerable<TestData>> GetLotsOfDataAsync(int count)
{
    var list = new List<TestData>();
    for (int i = 0; i < count; i++)
    {
        await Task.Delay(10);
        list.Add(new TestData($"{i}"));
    }
    return list;
}

async IAsyncEnumerable<TestData> GetLotsOfDataAsyncEnumerable(int count)
{
    for (int i = 0; i < count; i++)
    {
        await Task.Delay(10);
        yield return new TestData($"{i}");
    }
}

class TestData
{
    public string Field { get; }

    public TestData(string field)
    {
        Field = field;
    }
}
Run Code Online (Sandbox Code Playgroud)

计数路径变量允许控制我们想要在一次调用中检索多少数据。

我在 Windows 机器上使用curl命令对其进行了测试(这是解释如何使用curl测量性能的答案),100个条目的结果:

                       /100        /asyncEnumerable/100
     time_namelookup:  0.000045s   0.000034s
        time_connect:  0.000570s   0.000390s
     time_appconnect:  0.000000s   0.000000s
    time_pretransfer:  0.000648s   0.000435s
       time_redirect:  0.000000s   0.000000s
  time_starttransfer:  1.833341s   0.014880s
                       ---------------------
          time_total:  1.833411s  1.673477s
Run Code Online (Sandbox Code Playgroud)

这里重要的是要看到time_starttransfer,来自curl手册页

从开始到第一个字节即将传输所花费的时间(以秒为单位)。这包括 time_pretransfer 以及服务器计算结果所需的时间。

正如您所看到的/asyncEnumerable端点立即开始响应,当然,此类端点的客户端必须了解此类行为才能充分利用它。

这是它在命令行中的样子: curl 中的异步枚举调用

  • 如果是 json,客户端必须支持 json 的流式反序列化才能尽快开始处理它。相对较新的 _System.Text.Json_ 支持这一点。我认为这个[问题](/sf/ask/4095867541/)解决了这个问题。 (2认同)