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

Fre*_*ool 18 c# async-await asp.net-core-mvc asp.net-core

返回 anIAsyncEnumerable<T>和 aNotFoundResult但仍以异步方式处理的控制器操作的正确签名是什么?

我使用了这个签名并且它不能编译,因为它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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

我尝试使用这样的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Kir*_*kin 18

将 的实现传递IAsyncEnumerable<>Ok调用中的选项 2很好。ASP.NET Core 管道负责枚举并IAsyncEnumerable<>从 3.0 开始感知。

这是问题的调用,重复上下文:

return Ok(repository.GetAll(id)); // GetAll() returns an IAsyncEnumerable
Run Code Online (Sandbox Code Playgroud)

调用Ok创建 的实例OkObjectResult,该实例继承ObjectResult. 传递给的值Ok是 类型object,它保存在ObjectResultValue属性中。ASP.NET核心MVC使用命令模式,由此所述命令是的实现IActionResult和利用的实施方案中执行IActionResultExecutor<T>

对于ObjectResult,ObjectResultExecutor用于将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);
}
Run Code Online (Sandbox Code Playgroud)

如代码所示,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);
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

所述IAsyncEnumerable<>被枚举为List<>使用await foreach,其中,几乎从定义,不会阻塞的请求线程。正如 Panagiotis Kanavos 在对 OP 的评论中指出的那样,将响应发送回客户端之前,会完整执行此枚举。