从Asp.Net Core控制器返回IAsyncEnumerable <T>和NotFound


10

对于返回an IAsyncEnumerable<T>和a NotFoundResult但仍以异步方式处理的控制器操作,正确的签名是什么?

我使用了此签名,由于IAsyncEnumerable<T>无法等待,因此无法编译:

[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 = contentDeliveryManagementService.GetAll(id); // GetAll() returns an IAsyncEnumerable
    }
    catch (DeviceNotFoundException e)
    {
        return NotFound(e.Message);
    }

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

5
您要退回的多个MyObject物品相同id吗?通常,您不会NotFound为会返回an的内容发送“ a ” IEnumerable-它只是空的-否则您将返回带有所请求的id/ 的单个项目NotFound
crgolden

1
IAsyncEnumerable值得期待。使用await foreach(var item from ThatMethodAsync()){...}
Panagiotis Kanavos

如果要返回IAsyncEnumerable<MyObject>,只需返回结果,例如return objects。但这不会将HTTP操作转换为流gRPC或SignalR方法。中间件仍将使用数据并向客户端发送单个HTTP响应
Panagiotis Kanavos

选项2很好。ASP.NET Core管道负责枚举,并且IAsyncEnumerable从3.0开始就知道。
柯克·拉金

谢谢你们。我知道我没有在这里返回404,但这只是一个人为的例子。实际代码完全不同。@KirkLarkin很抱歉成为害虫,但您是否100%确定这不会造成任何阻碍?如果是,那么选项2是显而易见的解决方案。
弗雷德里克·傻瓜

Answers:


6

选项2可以将的实现传递IAsyncEnumerable<>Ok调用。ASP.NET Core管道负责枚举,并且IAsyncEnumerable<>从3.0开始就知道。

这是来自问题的电话,重复了以下内容:

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

调用Ok创建一个实例OkObjectResult,该实例继承ObjectResult。传递给的值Ok的类型为object,保存在ObjectResultValue属性中。ASP.NET Core MVC使用命令模式,其中命令是的实现,IActionResult并使用的实现执行IActionResultExecutor<T>

对于ObjectResultObjectResultExecutor用于将ObjectResult转换为HTTP响应。这是执行ObjectResultExecutor.ExecuteAsyncIAsyncEnumerable<>知晓:

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<>(详细信息隐藏在对的调用中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上面的代码段中是进行枚举的位置。它被埋了一点,但是您可以在这里看到源:

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<>其枚举为List<>using await foreach,几乎按照定义,它不会阻塞请求线程。正如Panagiotis Kanavos在对OP的评论中指出的那样,将响应发送回客户端之前,将完全执行此枚举。


感谢您提供详细的答案,柯克:)。我关心的是选项2中的action方法本身。我知道它的返回值将被异步枚举,但是它不返回Task对象。这个事实本身是否以任何方式阻碍了异步性?尤其是与类似的返回a的方法相比Task
弗雷德里克·傻瓜

1
不,很好。没有理由返回Task,因为方法本身不会执行任何异步工作。枚举是异步的,如上所述。您可以看到Task在执行时使用了ObjectResult
Kirk Larkin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.