【问题标题】:Returning IAsyncEnumerable<T> and NotFound from Asp.Net Core Controller从 Asp.Net 核心控制器返回 IAsyncEnumerable<T> 和 NotFound
【发布时间】:2020-05-07 12:35:21
【问题描述】:

对于返回 IAsyncEnumerable&lt;T&gt;NotFoundResult 但仍以异步方式处理的控制器操作,正确的签名是什么?

我使用了这个签名,但它无法编译,因为 IAsyncEnumerable&lt;T&gt; 不可等待:

[HttpGet]
public async Task<IActionResult> GetAll(Guid id)
{
    try
    {
        return Ok(await repository.GetAll(id)); // GetAll() returns an IAsyncEnumerable
    }
    catch (NotFoundException e)
    {
        return NotFound(e.Message);
    }
}

这个编译得很好,但它的签名不是异步的。所以我担心它是否会阻塞线程池线程:

[HttpGet]
public IActionResult GetAll(Guid id)
{
    try
    {
        return Ok(repository.GetAll(id)); // GetAll() returns an IAsyncEnumerable
    }
    catch (NotFoundException e)
    {
        return NotFound(e.Message);
    }
}

我尝试像这样使用await foreach 循环,但这显然也无法编译:

[HttpGet]
public async IAsyncEnumerable<MyObject> GetAll(Guid id)
{
    IAsyncEnumerable<MyObject> objects;
    try
    {
        objects = repository.GetAll(id); // GetAll() returns an IAsyncEnumerable
    }
    catch (NotFoundException e)
    {
        return NotFound(e.Message);
    }

    await foreach (var obj in objects)
    {
        yield return obj;
    }
}

【问题讨论】:

  • 您返回多个具有相同idMyObject 项目?通常你不会为返回 IEnumerable 的东西发送 NotFound - 它只是空的 - 或者你会返回带有请求的 id/NotFound 的单个项目。
  • IAsyncEnumerable 可以等待。使用await foreach(var item from ThatMethodAsync()){...}
  • 如果要返回IAsyncEnumerable&lt;MyObject&gt;,只需返回结果即可,例如return objects。不过,这不会将 HTTP 操作转换为流式 gRPC 或 SignalR 方法。中间件仍会使用数据并向客户端发送单个 HTTP 响应
  • 选项 2 很好。 ASP.NET Core 管道负责枚举,并且在 3.0 中支持 IAsyncEnumerable-aware。
  • 谢谢大家。我知道我在这里没有返回 404,但这只是一个人为的例子。实际代码完全不同。 @KirkLarkin 很抱歉成为害虫,但你 100% 确定这不会造成任何阻塞吗?如果是,那么选项 2 是显而易见的解决方案。

标签: c# asp.net-core async-await asp.net-core-mvc


【解决方案1】:

IAsyncEnumerable&lt;&gt; 的实现传递给Ok 调用的选项2 很好。 ASP.NET Core 管道负责枚举,并且在 3.0 中支持 IAsyncEnumerable&lt;&gt;-aware。

这是来自问题的调用,根据上下文重复:

return Ok(repository.GetAll(id)); // GetAll() returns an IAsyncEnumerable

Ok 的调用会创建一个OkObjectResult 的实例,该实例继承ObjectResult。传入Ok 的值是object 类型,它保存在ObjectResultValue 属性中。 ASP.NET Core MVC 使用command pattern,其中命令是IActionResult 的实现,并使用IActionResultExecutor&lt;T&gt; 的实现来执行。

对于ObjectResultObjectResultExecutor 用于将ObjectResult 转换为HTTP 响应。这是ObjectResultExecutor.ExecuteAsync 中的implementation,即IAsyncEnumerable&lt;&gt;-aware:

public virtual Task ExecuteAsync(ActionContext context, ObjectResult result)
{
    // ...

    var value = result.Value;

    if (value != null && _asyncEnumerableReaderFactory.TryGetReader(value.GetType(), out var reader))
    {
        return ExecuteAsyncEnumerable(context, result, value, reader);
    }

    return ExecuteAsyncCore(context, result, objectType, value);
}

如代码所示,检查Value 属性以查看它是否实现IAsyncEnumerable&lt;&gt;(详细信息隐藏在对TryGetReader 的调用中)。如果是,则调用ExecuteAsyncEnumerable,它会执行枚举,然后将枚举结果传递给ExecuteAsyncCore

private async Task ExecuteAsyncEnumerable(ActionContext context, ObjectResult result, object asyncEnumerable, Func<object, Task<ICollection>> reader)
{
    Log.BufferingAsyncEnumerable(Logger, asyncEnumerable);

    var enumerated = await reader(asyncEnumerable);
    await ExecuteAsyncCore(context, result, enumerated.GetType(), enumerated);
}

reader 上面的 sn -p 是枚举发生的地方。有点埋没,不过可以看源码here

private async Task<ICollection> ReadInternal<T>(object value)
{
    var asyncEnumerable = (IAsyncEnumerable<T>)value;
    var result = new List<T>();
    var count = 0;

    await foreach (var item in asyncEnumerable)
    {
        if (count++ >= _mvcOptions.MaxIAsyncEnumerableBufferLimit)
        {
            throw new InvalidOperationException(Resources.FormatObjectResultExecutor_MaxEnumerationExceeded(
                nameof(AsyncEnumerableReader),
                value.GetType()));
        }

        result.Add(item);
    }

    return result;
}

IAsyncEnumerable&lt;&gt; 使用await foreach 枚举为List&lt;&gt;,根据定义,这几乎不会阻塞请求线程。正如 Panagiotis Kanavos 在对 OP 的评论中指出的那样,此枚举是在 将响应发送回客户端之前完整执行的。

【讨论】:

    猜你喜欢
    • 2017-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多