简体   繁体   中英

Returning IAsyncEnumerable<T> and NotFound from Asp.Net Core Controller

What is the right signature for a controller action that returns an IAsyncEnumerable<T> and a NotFoundResult but is still processed in an async fashion?

I used this signature and it doesn't compile because IAsyncEnumerable<T> isn't awaitable:

[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);
    }
}

This one compiles fine but its signature isn't async. So I'm worried whether it'll block thread pool threads or not:

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

I tried using a await foreach loop on like this but that obviously wouldn't compile either:

[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;
    }
}

Option 2, which passes an implementation of IAsyncEnumerable<> into the Ok call, is fine. The ASP.NET Core plumbing takes care of the enumeration and is IAsyncEnumerable<> -aware as of 3.0.

Here's the call from the question, repeated for context:

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

The call to Ok creates an instance of OkObjectResult , which inherits ObjectResult . The value passed in to Ok is of type object , which is held in the ObjectResult 's Value property. ASP.NET Core MVC uses the command pattern , whereby the command is an implementation of IActionResult and is executed using an implementation of IActionResultExecutor<T> .

For ObjectResult , ObjectResultExecutor is used to turn the ObjectResult into a HTTP response. It's the implementation of ObjectResultExecutor.ExecuteAsync that is IAsyncEnumerable<> -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); }

As the code shows, the Value property is checked to see if it implements IAsyncEnumerable<> (the details are hidden in the call to TryGetReader ). If it does, ExecuteAsyncEnumerable is called, which performs the enumeration and then passes the enumerated result into 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 in the above snippet is where the enumeration occurs. It's buried a little, but you can see the source 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; }

The IAsyncEnumerable<> is enumerated into a List<> using await foreach , which, almost by definition, doesn't block a request thread. As Panagiotis Kanavos called out in a comment on the OP, this enumeration is performed in full before a response is sent back to the client.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM