Tại sao tôi nên tạo các thao tác WebAPI không đồng bộ thay vì các thao tác đồng bộ?


109

Tôi có thao tác sau trong API Web mà tôi đã tạo:

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public CartTotalsDTO GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh);
}

Lệnh gọi đến dịch vụ web này được thực hiện thông qua lệnh gọi Jquery Ajax theo cách này:

$.ajax({
      url: "/api/products/pharmacies/<%# Farmacia.PrimaryKeyId.Value.ToString() %>/page/" + vm.currentPage() + "/" + filter,
      type: "GET",
      dataType: "json",
      success: function (result) {
          vm.items([]);
          var data = result.Products;
          vm.totalUnits(result.TotalUnits);
      }          
  });

Tôi đã thấy một số nhà phát triển triển khai hoạt động trước đó theo cách này:

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return await Task.Factory.StartNew(() => delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh));
}

Tuy nhiên, phải nói rằng GetProductsWithHistory () là một hoạt động khá dài. Với vấn đề và bối cảnh của tôi, việc làm cho hoạt động webAPI không đồng bộ sẽ có lợi cho tôi như thế nào?


1
Phía máy khách sử dụng AJAX, vốn đã không đồng bộ. Bạn không cần dịch vụ cũng được viết dưới dạng một async Task<T>. Hãy nhớ rằng, AJAX được thực hiện trước khi TPL thậm chí tồn tại :)
Dominic Zukiewicz

65
Bạn cần hiểu lý do tại sao bạn triển khai bộ điều khiển không đồng bộ, nhiều người thì không. IIS có sẵn một số luồng giới hạn và khi tất cả được sử dụng, máy chủ không thể xử lý các yêu cầu mới. Với bộ điều khiển không đồng bộ khi một quy trình đang đợi I / O hoàn tất, luồng của nó sẽ được giải phóng để máy chủ sử dụng để xử lý các yêu cầu khác.
Matija Grcic

3
Bạn đã thấy những nhà phát triển nào làm được điều đó? Nếu có bất kỳ bài đăng trên blog hoặc bài báo nào đề xuất kỹ thuật đó, vui lòng đăng một liên kết.
Stephen Cleary

3
Bạn chỉ nhận được đầy đủ lợi ích của không đồng bộ nếu quy trình của bạn nhận biết không đồng bộ từ trên xuống (bao gồm bản thân ứng dụng web và bộ điều khiển của bạn) đối với bất kỳ hoạt động có thể chờ nào xảy ra bên ngoài quy trình của bạn (bao gồm độ trễ hẹn giờ, I / O tệp, truy cập DB, và các yêu cầu web mà nó đưa ra). Trong trường hợp này, người trợ giúp ủy nhiệm của bạn cần GetProductsWithHistoryAsync()trả lại Task<CartTotalsDTO>. Có thể có một lợi ích khi viết bộ điều khiển của bạn không đồng bộ nếu bạn định chuyển các lệnh gọi mà nó thực hiện thành không đồng bộ; thì bạn bắt đầu nhận được lợi ích từ các phần không đồng bộ khi bạn di chuyển phần còn lại.
Keith Robertson

1
Nếu quá trình bạn đang thực hiện đang diễn ra và chạm vào cơ sở dữ liệu thì chuỗi web của bạn chỉ chờ nó quay lại và giữ chuỗi đó. Nếu bạn đã đạt đến số luồng tối đa của mình và một yêu cầu khác đến, thì bạn phải đợi. Tại sao làm điều đó? Thay vào đó, bạn muốn giải phóng chuỗi đó khỏi bộ điều khiển của mình để một yêu cầu khác có thể sử dụng nó và chỉ chiếm một chuỗi web khác khi yêu cầu ban đầu của bạn từ cơ sở dữ liệu quay trở lại. msdn.microsoft.com/en-us/magazine/dn802603.aspx
user441521

Câu trả lời:


98

Trong ví dụ cụ thể của bạn, hoạt động hoàn toàn không đồng bộ nên những gì bạn đang làm là không đồng bộ hóa quá đồng bộ. Bạn chỉ phát hành một chủ đề và chặn một chủ đề khác. Không có lý do gì cho điều đó, bởi vì tất cả các luồng đều là luồng nhóm luồng (không giống như trong ứng dụng GUI).

Trong cuộc thảo luận của tôi về “không đồng bộ qua đồng bộ hóa”, tôi thực sự đề xuất rằng nếu bạn có một API được triển khai đồng bộ trong nội bộ, bạn không nên để lộ một đối tác không đồng bộ chỉ bao bọc phương thức đồng bộ Task.Run.

Từ Tôi có nên hiển thị trình bao bọc đồng bộ cho các phương thức không đồng bộ không?

Tuy nhiên, khi thực hiện các cuộc gọi WebAPI asynctrong đó có một hoạt động không đồng bộ thực tế (thường là I / O) thay vì chặn một luồng đang nằm và chờ kết quả, luồng sẽ quay trở lại nhóm luồng và do đó có thể thực hiện một số hoạt động khác. Hơn tất cả điều đó có nghĩa là ứng dụng của bạn có thể làm được nhiều việc hơn với ít tài nguyên hơn và điều đó cải thiện khả năng mở rộng.


3
@efaruk tất cả các luồng đều là luồng công nhân. Việc giải phóng một luồng ThreadPool và chặn một luồng khác là vô nghĩa.
i3arnon

1
@efaruk Tôi không chắc bạn đang muốn nói gì .. nhưng miễn là bạn đồng ý thì không có lý do gì để sử dụng async over sync trong WebAPI thì tốt thôi.
i3arnon

@efaruk "async over sync" (tức là await Task.Run(() => CPUIntensive())) vô dụng trong asp.net. Bạn không thu được gì từ việc làm đó. Bạn chỉ cần giải phóng một luồng ThreadPool để chiếm một luồng khác. Nó kém hiệu quả hơn chỉ đơn giản là gọi phương thức đồng bộ.
i3arnon

1
@efaruk Không, điều đó không hợp lý. Ví dụ bạn chạy các tác vụ độc lập tuần tự. Bạn thực sự cần phải đọc trên asyc / await trước khi đưa ra đề xuất. Bạn sẽ cần phải sử dụng await Task.WhenAllđể thực thi song song.
Søren Boisen

1
@efaruk Như Boisen giải thích, ví dụ của bạn không thêm bất kỳ giá trị nào mà chỉ cần gọi các phương thức đồng bộ này một cách tuần tự. Bạn có thể sử dụng Task.Runnếu bạn muốn song song tải của mình trên nhiều luồng, nhưng đó không phải là ý nghĩa của "async over sync". "async over sync" tham chiếu đến việc tạo một phương thức không đồng bộ như một trình bao bọc trên một phương thức đồng bộ. Bạn có thể xem phần trích dẫn trong câu trả lời của tôi.
i3arnon

1

Một cách tiếp cận có thể là (tôi đã sử dụng điều này thành công trong các ứng dụng của khách hàng) để có Dịch vụ Windows chạy các hoạt động dài dòng với các chuỗi công nhân và sau đó thực hiện việc này trong IIS để giải phóng các chuỗi cho đến khi hoạt động chặn hoàn tất: Lưu ý, điều này giả định kết quả được lưu trữ trong một bảng (các hàng được xác định bởi jobId) và một quy trình gọn gàng hơn sẽ dọn dẹp chúng vài giờ sau khi sử dụng.

Để trả lời câu hỏi, "Với vấn đề và bối cảnh của tôi, việc làm cho hoạt động webAPI không đồng bộ sẽ có lợi cho tôi như thế nào?" cho rằng đó là "một hoạt động khá dài" tôi đang nghĩ nhiều giây hơn là vài giây, cách tiếp cận này giải phóng các luồng IIS. Rõ ràng là bạn cũng phải chạy một dịch vụ windows mà bản thân nó cũng tốn tài nguyên nhưng cách tiếp cận này có thể ngăn chặn một loạt các truy vấn chậm ăn cắp luồng từ các phần khác của hệ thống.

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
        var jobID = Guid.NewGuid().ToString()
        var job = new Job
        {
            Id = jobId,
            jobType = "GetProductsWithHistory",
            pharmacyId = pharmacyId,
            page = page,
            filter = filter,
            Created = DateTime.UtcNow,
            Started = null,
            Finished = null,
            User =  {{extract user id in the normal way}}
        };
        jobService.CreateJob(job);

        var timeout = 10*60*1000; //10 minutes
        Stopwatch sw = new Stopwatch();
        sw.Start();
        bool responseReceived = false;
        do
        {
            //wait for the windows service to process the job and build the results in the results table
            if (jobService.GetJob(jobId).Finished == null)
            {
                if (sw.ElapsedMilliseconds > timeout ) throw new TimeoutException();
                await Task.Delay(2000);
            }
            else
            {
                responseReceived = true;
            }
        } while (responseReceived == false);

    //this fetches the results from the temporary results table
    return jobService.GetProductsWithHistory(jobId);
}
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.