Rückgabe von IAsyncEnumerable <T> und NotFound vom Asp.Net Core Controller

10

Was ist die richtige Signatur für eine Controller-Aktion, die ein IAsyncEnumerable<T>und ein zurückgibt, NotFoundResultaber dennoch asynchron verarbeitet wird?

Ich habe diese Signatur verwendet und sie wird nicht kompiliert, weil sie IAsyncEnumerable<T>nicht erwartet werden kann:

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

Dieser kompiliert gut, aber seine Signatur ist nicht asynchron. Ich mache mir also Sorgen, ob Thread-Pool-Threads blockiert werden oder nicht:

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

Ich habe versucht, eine await foreachSchleife wie diese zu verwenden, aber das würde natürlich auch nicht kompiliert:

[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;
    }
}
Friedrich der Narr
quelle
5
Sie senden mehrere MyObjectArtikel mit demselben zurück id? Normalerweise würden Sie keine NotFoundfür etwas senden, das eine zurückgibt IEnumerable- es wäre nur leer - oder Sie würden den einzelnen Artikel mit dem angeforderten id/ zurückgeben NotFound.
Crgolden
1
IAsyncEnumerableist zu erwarten. Verwenden Sie await foreach(var item from ThatMethodAsync()){...}.
Panagiotis Kanavos
Wenn Sie zurückkehren möchten, geben Sie IAsyncEnumerable<MyObject>einfach das Ergebnis zurück, z return objects. Dass nicht eine HTTP - Aktion zu einer Streaming - gRPC oder SignalR Methode obwohl konvertieren. Die Middleware verbraucht weiterhin die Daten und sendet eine einzelne HTTP-Antwort an den Client
Panagiotis Kanavos,
Option 2 ist in Ordnung. Das ASP.NET Core-Sanitär kümmert sich um die Aufzählung und ist IAsyncEnumerableab 3.0 bekannt.
Kirk Larkin
Danke Leute. Ich weiß, dass ich hier keine 404 zurückgegeben habe, aber dies ist nur ein erfundenes Beispiel. Der eigentliche Code ist ganz anders. @KirkLarkin Entschuldigung, dass Sie ein Schädling sind, aber sind Sie zu 100% sicher, dass dies keine Blockierung verursacht? Wenn ja, ist Option 2 die offensichtliche Lösung.
Frederick The Fool

Antworten:

6

Option 2, die eine Implementierung von IAsyncEnumerable<>in den OkAufruf übergibt , ist in Ordnung. Das ASP.NET Core-Sanitär kümmert sich um die Aufzählung und ist IAsyncEnumerable<>ab 3.0 bekannt.

Hier ist der Aufruf der Frage, der für den Kontext wiederholt wird:

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

Der Aufruf zum OkErstellen einer Instanz von OkObjectResult, die erbt ObjectResult. Der übergebene Wert Okist vom Typ object, der in der Eigenschaft von ObjectResult'' enthalten ist Value. ASP.NET Core MVC verwendet das Befehlsmuster , wobei der Befehl eine Implementierung von ist IActionResultund unter Verwendung einer Implementierung von ausgeführt wirdIActionResultExecutor<T> .

Für ObjectResult, ObjectResultExecutorwird die verwendete drehen ObjectResultin eine HTTP - Antwort. Es ist die Implementierung von ObjectResultExecutor.ExecuteAsyncdas ist 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);
}

Wie der Code zeigt, wird die ValueEigenschaft überprüft, um festzustellen, ob sie implementiert ist IAsyncEnumerable<>(die Details sind im Aufruf von verborgen TryGetReader). Wenn dies der Fall ist, ExecuteAsyncEnumerablewird aufgerufen, wodurch die Aufzählung durchgeführt und das aufgezählte Ergebnis an Folgendes übergeben wird 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);
}

readerIm obigen Snippet erfolgt die Aufzählung. Es ist ein wenig begraben, aber Sie können die Quelle sehen hier sehen :

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

Das IAsyncEnumerable<>wird in einem List<>using aufgezählt await foreach, das fast per Definition keinen Anfragethread blockiert. Wie Panagiotis Kanavos in einem Kommentar zum OP ausführte, wird diese Aufzählung zuvor vollständig durchgeführt eine Antwort an den Client zurückgesendet wird.

Kirk Larkin
quelle
Danke für die ausführliche Antwort Kirk :). Ein Problem, das ich habe, ist die Aktionsmethode selbst in Option 2. Ich habe verstanden, dass ihr Rückgabewert asynchron aufgelistet wird, aber kein TaskObjekt zurückgibt. Behindert diese Tatsache selbst in irgendeiner Weise die Asynchronität? Besonders im Vergleich zu einer ähnlichen Methode, die a zurückgab Task.
Frederick The Fool
1
Nein, es ist in Ordnung. Es gibt keinen Grund, a zurückzugeben Task, da die Methode selbst keine asynchrone Arbeit ausführt. Es ist die asynchrone Aufzählung, die wie oben beschrieben behandelt wird. Sie können sehen, dass Taskdort bei der Ausführung der verwendet wird ObjectResult.
Kirk Larkin