Restituzione di IAsyncEnumerable <T> e NotFound dal controller core Asp.Net


10

Qual è la firma giusta per un'azione del controller che restituisce un IAsyncEnumerable<T>e un NotFoundResultma viene comunque elaborata in modo asincrono?

Ho usato questa firma e non viene compilata perché IAsyncEnumerable<T>non è prevedibile:

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

Questo compila bene ma la sua firma non è asincrona. Quindi sono preoccupato se bloccherà o meno i thread del pool di thread:

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

Ho provato a usare un await foreachloop in questo modo ma ovviamente non si sarebbe nemmeno compilato:

[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
Restituisci più MyObjectarticoli con lo stesso id? Normalmente non invieresti NotFounda qualcosa che restituisce un IEnumerable- sarebbe solo vuoto - o restituiresti il ​​singolo articolo con il id/ richiesto NotFound.
Crgolden,

1
IAsyncEnumerableè attendibile. Usa await foreach(var item from ThatMethodAsync()){...}.
Panagiotis Kanavos,

Se si desidera tornare IAsyncEnumerable<MyObject>, è sufficiente restituire il risultato, ad es return objects. Tuttavia, ciò non converte un'azione HTTP in un metodo gRPC o SignalR in streaming. Il middleware consumerà comunque i dati e invierà una singola risposta HTTP al client
Panagiotis Kanavos

L'opzione 2 va bene. L'impianto idraulico ASP.NET Core si occupa dell'enumerazione ed è IAsyncEnumerableaggiornato alla versione 3.0.
Kirk Larkin il

Grazie ragazzi. So che non ho restituito un 404 qui, ma questo è solo un esempio inventato. Il codice attuale è abbastanza diverso. @KirkLarkin mi dispiace essere un parassita, ma sei sicuro al 100% che questo non causerà alcun blocco? Se sì, allora l'opzione 2 è la soluzione ovvia.
Frederick The Fool,

Risposte:


6

L'opzione 2, che passa un'implementazione IAsyncEnumerable<>nella Okchiamata, va bene. L'impianto idraulico ASP.NET Core si occupa dell'enumerazione ed è IAsyncEnumerable<>aggiornato alla versione 3.0.

Ecco la chiamata dalla domanda, ripetuta per il contesto:

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

La chiamata a Okcrea un'istanza di OkObjectResult, che eredita ObjectResult. Il valore passato a Okè di tipo object, che si svolge in ObjectResult's Valueproprietà. ASP.NET Core MVC utilizza il modello di comando , per cui il comando è un'implementazione di IActionResulte viene eseguito utilizzando un'implementazione di IActionResultExecutor<T>.

Per ObjectResult, ObjectResultExecutorviene utilizzato per trasformare il ObjectResultin una risposta HTTP. È l' implementazione di ObjectResultExecutor.ExecuteAsyncquesto è 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);
}

Come mostra il codice, la Valueproprietà viene controllata per vedere se implementa IAsyncEnumerable<>(i dettagli sono nascosti nella chiamata a TryGetReader). In tal caso, ExecuteAsyncEnumerableviene chiamato, che esegue l'enumerazione e quindi passa il risultato enumerato in 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);
}

readernello snippet sopra è dove si verifica l'enumerazione. È sepolto un po ', ma puoi vedere la fonte qui :

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

Il IAsyncEnumerable<>enumerata in una List<>utilizzando await foreach, che, quasi per definizione, non blocca un thread di richiesta. Come ha affermato Panagiotis Kanavos in un commento sull'OP, questa enumerazione viene eseguita per intero prima che una risposta venga inviata al client.


Grazie per la risposta dettagliata Kirk :). Una preoccupazione che ho riguarda il metodo di azione stesso nell'opzione 2. Ho capito che il suo valore di ritorno verrà enumerato in modo asincrono, ma non restituisce un Taskoggetto. Questo fatto ostacola in qualche modo l'asincronicità? Soprattutto rispetto, per esempio, a un metodo simile che ha restituito a Task.
Frederick The Fool,

1
No, va bene. Non c'è motivo di restituire a Task, perché il metodo stesso non esegue alcun lavoro asincrono. È l'enumerazione asincrona, che viene gestita come descritto sopra. Potete vedere che Taskè usato lì, nell'esecuzione di ObjectResult.
Kirk Larkin il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.