Разъяснение того, как 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++;
        }
    }
}
 

* Пожалуйста, имейте это в виду, экспериментируя с моим кодом, чтобы ваша машина не вылетела из строя :)

Другие вопросы по тегам