[英]Can (or should) I use IAsyncEnumerable<T> instead of Task<ActionResult<IEnumerable<T>>> in a Web API Controller
I currently have a web API that我目前有一个 Web API
FromSqlRaw(...).ToListAsync()
within a repositoryFromSqlRaw(...).ToListAsync()
获取一行数据Ok(data.ToArray())
as Task<ActionResult<IEnumerable<MyClass>>>
through a controller.Ok(data.ToArray())
作为Task<ActionResult<IEnumerable<MyClass>>>
返回。 Now I am wondering whether I should or can use IAsyncEnumerable as a return type.现在我想知道是否应该或可以使用 IAsyncEnumerable 作为返回类型。 The idea was to use this in the repository and the controller.
这个想法是在存储库和控制器中使用它。 However, in this (now decrepit) thread it states it should not be used.
但是,在这个(现在已经破旧的) 线程中,它声明不应该使用它。 the proposed solution here would be something like:
这里建议的解决方案类似于:
FromSqlRaw(...).AsNoTracking().AsAsyncEnumerable()
As for the Controller I want keep the response wrapped with ActionResult
to explicitly set the return code.至于控制器,我希望用
ActionResult
包裹响应以显式设置返回码。 However, that currently doesn't seem to work.但是,目前这似乎不起作用。
Should I just apply the solution for the repository and consume the result as a List in my controller or just keep it as it is?我应该将解决方案应用于存储库并将结果作为控制器中的列表使用还是保持原样?
The IAsyncEnumerable
gives you an interface for pull-based asynchronous data retrieval. IAsyncEnumerable
为您提供了基于拉取的异步数据检索的接口。 In other words this API represents an iterator where the next item is fetched asynchronously.换句话说,这个 API 表示一个迭代器,其中下一项是异步获取的。
This means that you are receiving the data in several rounds and each in an asynchronous fashion.这意味着您将分几轮接收数据,并且每轮都以异步方式接收。
Prior IAsyncEnumerable
you could use IEnumerable<Task<T>>
, which represents a bunch of asynchronous operations with return type T
.在
IAsyncEnumerable
之前,您可以使用IEnumerable<Task<T>>
,它表示一组返回类型为T
的异步操作。
Whereas Task<IEnumerable<T>>
represents a single asynchronous operation with a return type IEnumerable<T>
.而
Task<IEnumerable<T>>
表示返回类型为IEnumerable<T>
的单个异步操作。
Let's apply these knowledge to a WebAPI:让我们将这些知识应用到 WebAPI 中:
Task<ActionResult<T>>
and ActionResult<T>
.Task<ActionResult<T>>
和ActionResult<T>
之间没有区别。 It is an implementation detail from users` perspective. This means that the consumer of your API can't take advantage of IAsyncEnumerable
if it is exposed as an action result type.这意味着如果将
IAsyncEnumerable
作为操作结果类型公开,则 API 的使用者无法利用它。
In .net 6 IAsyncEnumerable handling for MVC was changed when using System.Text.Json :在 .net 6 中,使用System.Text.Json时, 更改了 MVC 的IAsyncEnumerable处理:
MVC no longer buffers IAsyncEnumerable instances.
MVC 不再缓冲 IAsyncEnumerable 实例。 Instead, MVC relies on the support that System.Text.Json added for these types.
相反,MVC 依赖于 System.Text.Json 为这些类型添加的支持。
It means that controller will start sending output immediately and a client may start process it as it receives chunks of the response.这意味着控制器将立即开始发送输出,并且客户端可以在收到响应块时开始处理它。
Here is an example with help of new minimal API :这是一个借助新的最小 API的示例:
Endpoint binding:端点绑定:
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();
Controller methods:控制器方法:
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;
}
}
count path variable allows to control how many data we want to retrieve in a single call.计数路径变量允许控制我们要在一次调用中检索多少数据。
I've tested it with curl command on a windows machine ( here is the answer explaining how to measure performance with curl ), results for 100 entries:我已经在 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
Important here to see is time_starttransfer , from curl manpage这里重要的是time_starttransfer ,来自 curl 手册页
The time, in seconds, it took from the start until the first byte was just about to be transferred.
从开始到第一个字节即将被传输所用的时间(以秒为单位)。 This includes time_pretransfer and also the time the server needed to calculate the result.
这包括 time_pretransfer 以及服务器计算结果所需的时间。
As you can see /asyncEnumerable endpoint started responding instantly, of course, clients of such endpoints have to be aware of such behavior to make good use of it.正如您所见, /asyncEnumerable端点立即开始响应,当然,此类端点的客户端必须意识到这种行为才能充分利用它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.