简体   繁体   English

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

[英]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

  • fetches a row of data using FromSqlRaw(...).ToListAsync() within a repository在存储库中使用FromSqlRaw(...).ToListAsync()获取一行数据
  • returns this data as 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 中:

  • From an HTTP consumer point of view there is no difference between Task<ActionResult<T>> and ActionResult<T> .从 HTTP 消费者的角度来看, Task<ActionResult<T>>ActionResult<T>之间没有区别。 It is an implementation detail from users` perspective.从用户的角度来看,这是一个实现细节。
  • A WebAPI Controller's action implements a request-response model . WebAPI 控制器的操作实现了请求-响应模型 Which means a single request is sent and a single response is received on the consumer-side.这意味着发送单个请求并在消费者端收到单个响应。
  • If a consumer calls the same action again then a new controller will be instantiated and will process that request.如果消费者再次调用相同的操作,则将实例化一个新的控制器并处理该请求。

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端点立即开始响应,当然,此类端点的客户端必须意识到这种行为才能充分利用它。

Here how it looks in a cmdline:这是它在 cmdline 中的外观: curl中的异步可枚举调用

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 我应该选择 IAsyncEnumerable<T> 或 IEnumerable <Task<T> &gt;? - should i choose IAsyncEnumerable<T> or IEnumerable<Task<T>>? 如何在任务中使用<actionresult<ienumerable<t> &gt;&gt; </actionresult<ienumerable<t> - How can I use in Task<ActionResult<IEnumerable<T>>> 我怎样才能“适应”一个任务<ienumerable<t> &gt; 到 IAsyncEnumerable<t> ? </t></ienumerable<t> - How can I "adapt" a Task<IEnumerable<T>> to IAsyncEnumerable<T>? IAsyncEnumerable 有什么区别<T>与 IEnumerable <Task<T> &gt;? - Whats the difference between IAsyncEnumerable<T> vs IEnumerable<Task<T>>? 我应该总是返回 IEnumerable<t> 而不是 IList<t> ?</t></t> - Should I always return IEnumerable<T> instead of IList<T>? IAsyncEnumerable 和有什么区别<t>和一个迭代器生成的 IEnumerable <task<t> &gt;? </task<t></t> - What's the difference between IAsyncEnumerable<T> and an iterator-generated IEnumerable<Task<T>>? 的ActionResult <IEnumerable<T> &gt;必须返回一个List <T> - ActionResult<IEnumerable<T>> has to return a List<T> 来自 IEnumerable <task<t> &gt; 到 IAsyncEnumerable<t> 通过 yield 在 Parallel.ForEach/Parallel.ForEachAsync 中返回会给出错误 CS1621 </t></task<t> - From IEnumerable<Task<T>> to IAsyncEnumerable<T> by yield returning inside a Parallel.ForEach/Parallel.ForEachAsync gives error CS1621 我不能在.ascx上使用IEnumerable()吗? - Can't I use IEnumerable() on .ascx? 使用 ActionResult 测试 web api controller - Testing web api controller with ActionResult
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM