2015-07-02 24 views
39

Tôi đang cố gắng tận dụng tính năng async/await của ASP.NET trong dự án API Web của tôi. Tôi không chắc chắn liệu nó sẽ tạo ra bất kỳ sự khác biệt nào về hiệu suất dịch vụ API Web của tôi hay không. Vui lòng tìm bên dưới quy trình làm việc và mã mẫu từ ứng dụng của tôi.Hiệu quả sử dụng async/await với ASP.NET Web API

làm việc Lưu lượng:

UI Application → Web API endpoint (điều khiển) → Gọi phương pháp trong Web lớp dịch vụ API → Gọi một dịch vụ web bên ngoài. (Ở đây chúng ta có sự tương tác DB, vv)

Bộ điều khiển:

public async Task<IHttpActionResult> GetCountries() 
{ 
    var allCountrys = await CountryDataService.ReturnAllCountries(); 

    if (allCountrys.Success) 
    { 
     return Ok(allCountrys.Domain); 
    } 

    return InternalServerError(); 
} 

Dịch vụ Lớp:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries() 
{ 
    var response = _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries"); 

    return Task.FromResult(response); 
} 

Tôi đã thử nghiệm mã trên và đang làm việc. Nhưng tôi không chắc liệu đó có phải là cách sử dụng chính xác của async/await hay không. Hãy chia sẻ suy nghĩ của bạn.

Trả lời

84

Tôi không chắc chắn liệu nó sẽ tạo ra bất kỳ sự khác biệt nào về hiệu suất của API của tôi hay không.

Hãy nhớ rằng lợi ích chính của mã không đồng bộ ở phía máy chủ là khả năng mở rộng. Nó sẽ không kỳ diệu làm cho yêu cầu của bạn chạy nhanh hơn. Tôi bao gồm một số "tôi nên sử dụng async" cân nhắc trong số article on async ASP.NET của mình.

Tôi nghĩ trường hợp sử dụng của bạn (gọi các API khác) rất phù hợp với mã không đồng bộ, chỉ cần nhớ rằng "không đồng bộ" không có nghĩa là "nhanh hơn".Cách tiếp cận tốt nhất là trước hết hãy làm cho UI phản hồi và không đồng bộ của bạn; điều này sẽ làm cho ứng dụng của bạn cảm thấy nhanh hơn ngay cả khi nó hơi chậm hơn.

Theo như mã đi, đây không phải là không đồng bộ:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries() 
{ 
    var response = _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries"); 
    return Task.FromResult(response); 
} 

Bạn sẽ cần một việc thực hiện thực sự không đồng bộ để có được những lợi ích khả năng mở rộng của async:

public async Task<BackOfficeResponse<List<Country>>> ReturnAllCountriesAsync() 
{ 
    return await _service.ProcessAsync<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries"); 
} 

Hoặc (nếu bạn logic trong phương pháp này thực sự chỉ là thông qua):

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountriesAsync() 
{ 
    return _service.ProcessAsync<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries"); 
} 

Lưu ý rằng nó dễ dàng hơn để làm việc từ "bên trong ra" chứ không phải là "bên ngoài trong" như thế này. Nói cách khác, không bắt đầu với một hành động điều khiển không đồng bộ và sau đó buộc các phương thức hạ lưu không đồng bộ. Thay vào đó, hãy xác định các hoạt động không đồng bộ một cách tự nhiên (gọi API bên ngoài, truy vấn cơ sở dữ liệu, v.v.) và làm cho chúng không đồng bộ ở mức thấp nhất trước tiên (Service.ProcessAsync). Sau đó, hãy để async nhỏ giọt, làm cho hành động của trình điều khiển của bạn không đồng bộ như bước cuối cùng.

Và trong mọi trường hợp, bạn nên sử dụng Task.Run trong trường hợp này.

+4

Cảm ơn Stephen vì những nhận xét quý giá của bạn. Tôi đã thay đổi tất cả các phương thức lớp dịch vụ của mình thành không đồng bộ và bây giờ tôi gọi cuộc gọi REST bên ngoài của mình bằng phương thức ExecuteTaskAsync và đang hoạt động như mong đợi. Cũng cảm ơn cho bài đăng trên blog của bạn về async và Tasks. Nó thực sự đã giúp tôi rất nhiều để có được một sự hiểu biết ban đầu. – arp

+3

Bạn có thể giải thích tại sao bạn không sử dụng Task.Run ở đây? – Maarten

+0

@ Marta: Tôi giải thích nó (một thời gian ngắn) trong [bài viết tôi liên kết tới] (https://msdn.microsoft.com/en-us/magazine/dn802603.aspx). Tôi đi vào chi tiết hơn [trên blog của tôi] (http://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html). –

-3

tôi sẽ thay đổi lớp dịch vụ của bạn:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries() 
{ 
    return Task.Run(() => 
    { 
     return _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries"); 
    }  
} 

khi bạn có nó, bạn vẫn chạy _service.Process cuộc gọi của bạn đồng bộ và đạt được rất ít hoặc không có lợi ích từ chờ nó.

Với cách tiếp cận này, bạn đang gói cuộc gọi có khả năng chậm trong một Task, bắt đầu cuộc gọi và trả lại để chờ. Bây giờ bạn có được lợi ích của việc chờ đợi Task.

+2

Theo blog [link] (http://www.ben-morris.com/why-you-shouldnt-create-asynchronous-wrappers-with-task-run/), tôi có thể thấy rằng ngay cả khi chúng tôi sử dụng Task.Run phương pháp vẫn chạy đồng bộ? – arp

+0

Hmm. Có rất nhiều sự khác biệt giữa mã trên và liên kết đó. 'Service' của bạn không phải là một phương thức async, và không được chờ đợi trong chính cuộc gọi' Service'. Tôi có thể hiểu được quan điểm của anh ấy, nhưng tôi không tin nó được áp dụng ở đây. – Jonesopolis

+4

'Task.Run' nên tránh trên ASP.NET. Cách tiếp cận này sẽ loại bỏ tất cả các lợi ích từ 'async'/'await' và thực sự hoạt động tệ hơn dưới tải chỉ là một cuộc gọi đồng bộ. –

3

Chính xác, nhưng có lẽ không hữu ích.

Vì không có gì để chờ - không có cuộc gọi chặn API có thể hoạt động không đồng bộ - khi đó bạn đang thiết lập cấu trúc để theo dõi hoạt động không đồng bộ (có phí) nhưng sau đó không sử dụng khả năng đó.

Ví dụ, nếu các lớp dịch vụ được thực hiện các hoạt động DB với Entity Framework mà hỗ trợ các cuộc gọi không đồng bộ:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries() 
{ 
    using (db = myDBContext.Get()) { 
     var list = await db.Countries.Where(condition).ToListAsync(); 

     return list; 
    } 
} 

Bạn sẽ cho phép các sợi nhân để làm cái gì khác trong khi db được truy vấn (và do đó có thể xử lý một yêu cầu khác).

Chờ đợi có xu hướng trở thành thứ cần phải giảm hết sức: rất khó để phù hợp với hệ thống hiện tại.

Các vấn đề liên quan