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.