Разъяснение того, как IAsyncEnumerable работает с веб-API ASP.NET
Я столкнулся с интересным поведением при изучении IAsyncEnumerable в проекте веб-API ASP.NET. Рассмотрим следующие примеры кода:
// Code Sample 1
[HttpGet]
public async IAsyncEnumerable<int> GetAsync()
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(1000);
yield return i;
}
}
// Code Sample 2
[HttpGet]
public async IAsyncEnumerable<string> GetAsync()
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(1000);
yield return i.ToString();
}
}
Пример 1 (массив int) возвращает {}
как результат JSON.
Образец 2 возвращает ожидаемый результат ["0","1","2","3","4","5","6","7","8","9"]
. Однако весь массив JSON возвращается сразу после 10 секунд ожидания. Разве его не следует возвращать, когда данные становятся доступными, как ожидалось, из интерфейса IAsyncEnumerable? Или есть какой-то конкретный способ использования этого веб-API?
2 ответа
Вызов веб-API не будет возвращать частичный json каждую секунду. Это json-сериализатор должен ждать 10x1секунду (или код, вызывающий json-сериализатор, который является частью ASP .NET). Как только код фреймворка и сериализатор получат все данные, они будут сериализованы и переданы клиенту в виде единого ответа.
В типах возврата действий контроллера в веб-API ASP.NET Core мы можем прочитать:
В ASP.NET Core 3.0 и более поздних версиях возврат IAsyncEnumerable из действия:
- Больше не приводит к синхронной итерации.
- Становится столь же эффективным, как и возврат IEnumerable.
ASP.NET Core 3.0 и более поздние версии буферизируют результат следующего действия перед передачей его в сериализатор:
public IEnumerable<Product> GetOnSaleProducts() =>
_context.Products.Where(p => p.IsOnSale);
Действительно, в ASP.NET Core 5 экземпляры типа обрабатывались путем одновременной буферизации последовательности в памяти и форматирования буферизованной коллекции. Это объясняет, почему вы не получили частичных результатов.
Однако с ASP.NET Core 6.0 это станет возможным !
В ASP.NET Core 6 при форматировании с использованием System.Text.Json MVC больше не буферизует экземпляры IAsyncEnumerable. Вместо этого MVC полагается на поддержку, которую System.Text.Json добавил для этих типов (ссылка)
Релиз для ASP.NET Core 6 запланирован на ноябрь 2021 года (для справки). Уже сейчас можно протестировать новое поведение с помощью предварительных версий. Я успешно протестировал следующий код, используя предварительную версию 6.0.100-preview.6.21355.2. Код создает бесконечный поток целых чисел и возвращает его через контроллер, используя
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace dot_net_api_streaming.Controllers
{
[ApiController]
[Route("[controller]")]
public class LoopController : ControllerBase
{
[HttpGet]
public IAsyncEnumerable<int> Get()
{
return GetInfiniteInts();
}
private async IAsyncEnumerable<int> GetInfiniteInts()
{
int index = 0;
while (true)
yield return index++;
}
}
}
* Пожалуйста, имейте это в виду, экспериментируя с моим кодом, чтобы ваша машина не вылетела из строя :)