Trả lại IAsyncEnableable <T> và NotFound từ Asp.Net Core Controller


10

Chữ ký phù hợp cho hành động của bộ điều khiển trả về một IAsyncEnumerable<T>và một NotFoundResultnhưng vẫn được xử lý theo kiểu không đồng bộ?

Tôi đã sử dụng chữ ký này và nó không được biên dịch vì IAsyncEnumerable<T>không thể chờ đợi:

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

Cái này biên dịch tốt nhưng chữ ký của nó không đồng bộ. Vì vậy, tôi lo lắng liệu nó sẽ chặn chủ đề nhóm chủ đề hay không:

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

Tôi đã thử sử dụng một await foreachvòng lặp như thế này nhưng rõ ràng nó cũng không được biên dịch:

[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
Bạn đang trả lại nhiều MyObjectmặt hàng với cùng một id? Thông thường Bạn sẽ không gửi một NotFoundcái gì đó trả về IEnumerable- nó sẽ trống - hoặc bạn sẽ trả lại một mục duy nhất với yêu cầu id/ NotFound.
crgolden

1
IAsyncEnumerableđang chờ đợi Sử dụng await foreach(var item from ThatMethodAsync()){...}.
Panagiotis Kanavos

Nếu bạn muốn trả về IAsyncEnumerable<MyObject>, chỉ cần trả về kết quả, vd return objects. Tuy nhiên, điều đó sẽ không chuyển đổi một hành động HTTP thành một phương thức gRPC hoặc SignalR phát trực tuyến. Phần mềm trung gian vẫn sẽ tiêu thụ dữ liệu và gửi một phản hồi HTTP cho khách hàng
Panagiotis Kanavos

Lựa chọn 2 là tốt. Hệ thống ống nước ASP.NET Core đảm nhiệm việc liệt kê và được IAsyncEnumerablebiết đến từ 3.0.
Kirk Larkin

Cảm ơn các bạn. Tôi biết tôi không trả lại 404 ở đây, nhưng đây chỉ là một ví dụ giả định. Mã thực tế là khá khác nhau. @KirkLarkin xin lỗi vì là một loài gây hại, nhưng bạn có chắc chắn 100% điều này sẽ không gây ra bất kỳ sự ngăn chặn nào không? Nếu có, thì Lựa chọn 2 là giải pháp rõ ràng.
Frederick The Fool

Câu trả lời:


6

Phương án 2, mà đi một thực hiện IAsyncEnumerable<>vào Okcuộc gọi, là tốt. Hệ thống ống nước ASP.NET Core đảm nhiệm việc liệt kê và được IAsyncEnumerable<>biết đến từ 3.0.

Đây là cuộc gọi từ câu hỏi, lặp đi lặp lại cho bối cảnh:

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

Cuộc gọi để Oktạo một thể hiện của OkObjectResult, kế thừa ObjectResult. Giá trị được truyền vào Oklà loại object, được giữ trong thuộc tính ObjectResultcủa Value. ASP.NET Core MVC sử dụng mẫu lệnh , theo đó lệnh là một triển khai IActionResultvà được thực thi bằng cách sử dụng thực hiện IActionResultExecutor<T>.

Đối với ObjectResult, ObjectResultExecutorđược sử dụng để biến ObjectResultphản hồi HTTP. Đó là thực hiện các ObjectResultExecutor.ExecuteAsyncnghĩa là 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);
}

Như mã hiển thị, thuộc Valuetính được kiểm tra để xem nếu nó thực hiện IAsyncEnumerable<>(các chi tiết được ẩn trong lệnh gọi đến TryGetReader). Nếu có, ExecuteAsyncEnumerableđược gọi, thực hiện phép liệt kê và sau đó chuyển kết quả liệt kê vào 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);
}

readertrong đoạn trích trên là nơi xảy ra phép liệt kê. Nó bị chôn vùi một chút, nhưng bạn có thể thấy nguồn ở đây :

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

Cái IAsyncEnumerable<>được liệt kê thành một List<>cách sử dụng await foreach, gần như theo định nghĩa, không chặn một luồng yêu cầu. Như Panagiotis Kanavos đã gọi trong một bình luận về OP, việc liệt kê này được thực hiện đầy đủ trước khi phản hồi được gửi lại cho khách hàng.


Cảm ơn câu trả lời chi tiết Kirk :). Một mối quan tâm tôi có là với chính phương thức hành động trong tùy chọn 2. Tôi hiểu rằng giá trị trả về của nó sẽ được liệt kê không đồng bộ, nhưng nó không trả về một Taskđối tượng. Có phải thực tế đó cản trở sự bất thường trong bất kỳ cách nào? Đặc biệt so với, nói, một phương pháp tương tự mà trả về a Task.
Frederick The Fool

1
Không, nó ổn. Không có lý do để trả về a Task, vì bản thân phương thức này không thực hiện bất kỳ công việc không đồng bộ nào. Đó là phép liệt kê không đồng bộ, được xử lý như mô tả ở trên. Bạn có thể thấy rằng Taskđược sử dụng ở đó, trong việc thực hiện ObjectResult.
Kirk Larkin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.