ส่งคืน IAsyncEnumerable <T> และ NotFound จาก Asp.Net Core Controller


10

อะไรคือลายเซ็นที่ถูกต้องสำหรับแอ็คชั่นคอนโทรลเลอร์ที่ส่งกลับค่าIAsyncEnumerable<T>a และ a NotFoundResultแต่ยังคงถูกประมวลผลในรูปแบบ async?

ฉันใช้ลายเซ็นนี้และไม่ได้รวบรวมเพราะ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
crgolden

1
IAsyncEnumerableกำลังรอคอย await foreach(var item from ThatMethodAsync()){...}ใช้
Panagiotis Kanavos

หากคุณต้องการที่จะกลับมาเพียงแค่กลับผลเช่นIAsyncEnumerable<MyObject> return objectsแต่นั่นจะไม่แปลงการกระทำ HTTP เป็นวิธีสตรีมมิ่ง gRPC หรือ SignalR มิดเดิลแวร์จะยังคงใช้ข้อมูลและส่งการตอบกลับ HTTP เดียวไปยังไคลเอนต์
Panagiotis Kanavos

ตัวเลือก 2 ไม่เป็นไร การประปาหลักของ ASP.NET จะดูแลการแจงนับและรับIAsyncEnumerableรู้ถึง 3.0
Kirk Larkin

ขอบคุณเพื่อน. ฉันรู้ว่าฉันไม่ได้คืนค่า 404 ที่นี่ แต่นี่เป็นเพียงตัวอย่างที่ได้วางแผนไว้ รหัสจริงแตกต่างกันมาก @ KirkLarkin ขออภัยที่เป็นศัตรูพืช แต่คุณแน่ใจ 100% ว่าจะไม่ทำให้เกิดการบล็อกหรือไม่ ถ้าใช่ตัวเลือกที่ 2 คือทางออกที่ชัดเจน
Frederick The Fool

คำตอบ:


6

ตัวเลือกที่ 2 ซึ่งผ่านการใช้งานของIAsyncEnumerable<>การOkโทรเป็นเรื่องปกติ การประปาหลักของ ASP.NET จะดูแลการแจงนับและรับIAsyncEnumerable<>รู้ถึง 3.0

นี่คือการโทรจากคำถามที่ทำซ้ำสำหรับบริบท:

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

การเรียกเพื่อOkสร้างอินสแตนซ์OkObjectResultซึ่งสืบทอดObjectResultมา ค่าผ่านในการOkเป็นประเภทobjectซึ่งจะจัดขึ้นในObjectResult's Valueคุณสมบัติ ASP.NET MVC หลักใช้รูปแบบคำสั่งโดยคำสั่งคือการดำเนินการและจะถูกดำเนินการโดยใช้การดำเนินการของIActionResultIActionResultExecutor<T>

สำหรับObjectResult, ObjectResultExecutorใช้เพื่อเปลี่ยนการObjectResultตอบสนองเป็น HTTP มันเป็นการนำไปปฏิบัติของ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ซึ่งเกือบตามคำจำกัดความไม่ได้บล็อกเธรดการร้องขอ เมื่อ Panagiotis Kanavos เรียกใช้ในความคิดเห็นบน OP การแจงนับนี้จะดำเนินการเต็มก่อนที่จะส่งคำตอบกลับไปยังลูกค้า


ขอบคุณสำหรับคำตอบ Kirk อย่างละเอียด :) สิ่งหนึ่งที่ฉันกังวลก็คือด้วยวิธีการดำเนินการเองในตัวเลือก 2 ฉันเข้าใจว่าค่าส่งคืนของมันจะถูกแจกแจงแบบอะซิงโครนัส แต่มันไม่ได้ส่งคืนTaskวัตถุ ความจริงข้อนี้เองนั้นเป็นอุปสรรคต่อความไม่สงบในทางใดทางหนึ่งหรือไม่? โดยเฉพาะอย่างยิ่งเมื่อเทียบกับการพูด, Taskวิธีการที่คล้ายกันที่ส่งกลับ
Frederick The Fool

1
ไม่เป็นไร ไม่มีเหตุผลในการส่งคืนTaskเนื่องจากวิธีการเองไม่ทำงานแบบอะซิงโครนัส มันคือการแจงนับที่ไม่ตรงกันซึ่งจัดการตามที่อธิบายไว้ข้างต้น คุณจะเห็นว่ามีการใช้อยู่ที่นั่นในการดำเนินการของTask ObjectResult
Kirk Larkin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.