Повернення IAsyncEnumerable <T> і NotFound з контролера Asp.Net Core


10

Який правильний підпис для дії контролера, який повертає значення IAsyncEnumerable<T>a і a, NotFoundResultале все ще обробляється асинхронним способом?

Я використав цей підпис, і він не компілюється, оскільки 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);
    }
}

Цей складається добре, але його підпис не є асинхронним. Тож я переживаю, чи заблокує це потоки пулу потоків чи ні:

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

Я спробував використовувати await foreachцикл на цьому, але це явно не збирається:

[HttpGet]
public async IAsyncEnumerable<MyObject> GetAll(Guid id)
{
    IAsyncEnumerable<MyObject> objects;
    try
    {
        objects = contentDeliveryManagementService.GetAll(id); // GetAll() returns an IAsyncEnumerable
    }
    catch (DeviceNotFoundException e)
    {
        return NotFound(e.Message);
    }

    await foreach (var obj in objects)
    {
        yield return obj;
    }
}

5
Ви повертаєте кілька MyObjectпредметів з одним і тим же id? Зазвичай ви не відправляєте NotFoundте, що повертає IEnumerable- воно буде просто порожнім - або ви повернете один елемент із запитаним id/ NotFound.
криптовалюта

1
IAsyncEnumerableчекає. Використовуйте await foreach(var item from ThatMethodAsync()){...}.
Панайотис Канавос

Якщо ви хочете повернутись IAsyncEnumerable<MyObject>, просто поверніть результат, наприклад return objects. Це не перетворить дію HTTP в потоковий gRPC або метод SignalR. Посереднє програмне забезпечення все ще буде споживати дані та надсилатиме клієнту єдину відповідь HTTP
Panagiotis Kanavos

Варіант 2 прекрасний. Сантехніка ASP.NET Core піклується про перерахування і є IAsyncEnumerableвідомою з 3.0.
Кірк Ларкін

Дякую, хлопці. Я знаю, що я не поверну 404 тут, але це лише надуманий приклад. Фактичний код зовсім інший. @KirkLarkin вибачте за шкідників, але ви впевнені на 100%, що це не спричинить блокування? Якщо так, то варіант 2 - це очевидне рішення.
Фредерік Дурень

Відповіді:


6

Варіант 2, який передає реалізацію IAsyncEnumerable<>в Okдзвінок, чудовий. Сантехніка ASP.NET Core піклується про перерахування і є IAsyncEnumerable<>відомою з 3.0.

Ось дзвінок із запитання, повторений у контексті:

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

Заклик до Okстворення екземпляра OkObjectResult, який успадковується ObjectResult. Значення, передане в, Okмає тип object, який зберігається у властивості ObjectResult's Value. ASP.NET Core MVC використовує шаблон команди , згідно з яким команда є реалізацією IActionResultта виконується за допомогою реалізації IActionResultExecutor<T>.

Для ObjectResult, ObjectResultExecutorвикористовується для перетворення ObjectResultHTTP-відповіді. Це реалізація того, ObjectResultExecutor.ExecuteAsyncщо IAsyncEnumerable<>:

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

Як показує код, 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);
}

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

IAsyncEnumerable<>Перераховується в List<>використанні await foreach, що, майже за визначенням, не блокує потік запиту. Як зауважив Панайотис Канавос у коментарі до ОП, це перерахування проводиться в повному обсязі до того, як відповідь буде відправлена ​​назад клієнту.


Дякую за детальну відповідь Кірк :). Одне занепокоєння викликає сам метод дії у варіанті 2. Я розумів, що його повернене значення буде перераховано асинхронно, але він не повертає Taskоб'єкт. Чи сам цей факт перешкоджає асинхронності? Особливо порівняно з, скажімо, подібним методом, який повернув a Task.
Фредерік Дурень

1
Ні, це добре. Немає підстав повертати а Task, оскільки сам метод не виконує жодної асинхронної роботи. Це асинхронний перелік, який обробляється так, як описано вище. Ви можете бачити, що Taskвикористовується там, у виконанні ObjectResult.
Кірк Ларкін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.