Khi nào tôi nên sử dụng Bộ điều khiển Async trong ASP.NET MVC?


215

Tôi có một số lo ngại khi sử dụng các hành động không đồng bộ trong ASP.NET MVC. Khi nào nó cải thiện hiệu suất của các ứng dụng của tôi và khi nào thì không ?

  1. Có tốt không khi sử dụng hành động async ở mọi nơi trong ASP.NET MVC?
  2. Về các phương thức có thể chờ đợi: tôi có nên sử dụng từ khóa async / await khi tôi muốn truy vấn cơ sở dữ liệu (thông qua EF / NHibernate / ORM khác) không?
  3. Bao nhiêu lần tôi có thể sử dụng các từ khóa đang chờ để truy vấn cơ sở dữ liệu không đồng bộ trong một phương thức hành động?

Câu trả lời:


109

Các phương thức hành động không đồng bộ rất hữu ích khi một hành động phải thực hiện một số hoạt động chạy dài độc lập.

Một cách sử dụng điển hình cho lớp AsyncControll là các cuộc gọi dịch vụ Web chạy dài.

Các cuộc gọi cơ sở dữ liệu của tôi có nên không đồng bộ?

Nhóm luồng IIS thường có thể xử lý nhiều yêu cầu chặn đồng thời hơn một máy chủ cơ sở dữ liệu. Nếu cơ sở dữ liệu là nút cổ chai, các cuộc gọi không đồng bộ sẽ không tăng tốc độ phản hồi của cơ sở dữ liệu. Nếu không có cơ chế điều chỉnh, việc gửi nhiều công việc đến máy chủ cơ sở dữ liệu bị quá tải một cách hiệu quả bằng cách sử dụng các cuộc gọi không đồng bộ chỉ đơn thuần chuyển thêm gánh nặng cho cơ sở dữ liệu. Nếu DB của bạn là nút cổ chai, các cuộc gọi không đồng bộ sẽ không phải là viên đạn ma thuật.

Bạn nên xem qua 12 tài liệu tham khảo

Bắt nguồn từ ý kiến ​​của @PanagiotisKanavos:

Hơn nữa, async không có nghĩa là song song. Việc thực thi không đồng bộ giải phóng một luồng luồng có giá trị khỏi việc chặn tài nguyên bên ngoài, không có chi phí phức tạp hoặc hiệu năng. Điều này có nghĩa là cùng một máy IIS có thể xử lý các yêu cầu đồng thời hơn, không phải là nó sẽ chạy nhanh hơn.

Bạn cũng nên xem xét rằng việc chặn các cuộc gọi bắt đầu bằng một spinwait chuyên sâu về CPU. Trong thời gian căng thẳng, việc chặn các cuộc gọi sẽ dẫn đến sự chậm trễ leo thang và tái chế nhóm ứng dụng. Các cuộc gọi không đồng bộ chỉ đơn giản là tránh điều này


27
Liên quan đến async, IIS và cơ sở dữ liệu, bài đăng trên blog là rất cũ và kết luận rút ra ở đây là sai. IIS phải tải máy chủ lớn hơn nhiều so với cơ sở dữ liệu, các luồng xử lý được giới hạn và hàng đợi yêu cầu dài có thể dẫn đến 500. Bất cứ điều gì giải phóng một luồng trong khi chờ IO đều có lợi. Ngay cả các liên kết không phải là về những gì OP yêu cầu. Trong thực tế, các tác giả tương tự đã viết rằng bạn nên sử dụng các phương thức DB không đồng bộ bất cứ nơi nào bạn có thể
Panagiotis Kanavos

30
Hơn nữa, async không có nghĩa là song song. Việc thực thi không đồng bộ giải phóng một luồng luồng có giá trị khỏi việc chặn tài nguyên bên ngoài, không có chi phí phức tạp hoặc hiệu năng. Điều này có nghĩa là cùng một máy IIS có thể xử lý các yêu cầu đồng thời hơn , không phải là nó sẽ chạy nhanh hơn.
Panagiotis Kanavos

5
Bạn cũng nên xem xét rằng việc chặn các cuộc gọi bắt đầu bằng một spinwait chuyên sâu về CPU. Trong thời gian căng thẳng, việc chặn các cuộc gọi sẽ dẫn đến sự chậm trễ leo thang và tái chế nhóm ứng dụng. Các cuộc gọi không đồng bộ chỉ đơn giản là tránh điều này
Panagiotis Kanavos

1
Vì đây là câu trả lời được chấp nhận và do đó ở trên cùng đối với hầu hết mọi người, nên có thể khôn ngoan khi xóa đoạn thứ hai thứ hai liên quan đến thời gian thực hiện và chạy song song, không liên quan gì đến async. Tôi nhận ra có một lưu ý ở cuối, nhưng nó khá gây hiểu lầm.
Cướp

1
Việc thực thi không đồng bộ chỉ giải phóng một luồng trong trường hợp khi nó thực sự không đồng bộ xuống tận cùng của triển khai và sử dụng một số cơ chế gọi lại để báo hiệu sự sẵn sàng và để quay một luồng mới để chuyển ngữ cảnh và xử lý kết quả. Vì vậy, tôi không chắc trong trường hợp nào việc chuyển đổi ngữ cảnh / tạo chủ đề mới này hiệu quả hơn là chỉ chờ kết quả trong một luồng? Các cuộc gọi dịch vụ web chạy dài dù sao cũng là một ý tưởng tồi, tôi thích giảm tải nó cho một dịch vụ nền đáng tin cậy và để khách hàng kiểm tra kết quả, có thể đến trong vài phút, vài giờ hoặc vài ngày.
JustAMartin

229

Bạn có thể thấy bài viết MSDN của tôi về chủ đề này hữu ích ; Tôi đã dành rất nhiều không gian trong bài viết đó mô tả khi nào bạn nên sử dụng asynctrên ASP.NET, không chỉ là cách sử dụng asynctrên ASP.NET.

Tôi có một số lo ngại khi sử dụng các hành động không đồng bộ trong ASP.NET MVC. Khi nó cải thiện hiệu suất của các ứng dụng của tôi và khi nào - không.

Đầu tiên, hiểu rằng async/ awaitlà tất cả về giải phóng chủ đề . Trên các ứng dụng GUI, chủ yếu là giải phóng luồng GUI để trải nghiệm người dùng tốt hơn. Trên các ứng dụng máy chủ (bao gồm ASP.NET MVC), chủ yếu là giải phóng chuỗi yêu cầu để máy chủ có thể mở rộng quy mô.

Đặc biệt, nó sẽ không:

  • Làm cho yêu cầu cá nhân của bạn hoàn thành nhanh hơn. Trong thực tế, họ sẽ hoàn thành (chỉ một chút thiếu niên) chậm hơn.
  • Quay trở lại trình gọi / trình duyệt khi bạn nhấn một await. awaitchỉ "nhường" cho nhóm luồng ASP.NET, không cho trình duyệt.

Câu hỏi đầu tiên là - có tốt không khi sử dụng hành động async ở mọi nơi trong ASP.NET MVC?

Tôi muốn nói rằng thật tốt khi sử dụng nó ở mọi nơi bạn đang thực hiện I / O. Nó có thể không nhất thiết có lợi , mặc dù (xem bên dưới).

Tuy nhiên, thật tệ khi sử dụng nó cho các phương thức gắn với CPU. Đôi khi các nhà phát triển nghĩ rằng họ có thể nhận được lợi ích asyncbằng cách chỉ cần gọi Task.Runtrong bộ điều khiển của họ, và đây là một ý tưởng khủng khiếp. Bởi vì mã đó kết thúc việc giải phóng chuỗi yêu cầu bằng cách chiếm một luồng khác, do đó không có lợi ích gì cả (và trên thực tế, họ đang phải chịu hình phạt của các chuyển đổi luồng bổ sung)!

Tôi có nên sử dụng async / await từ khóa khi tôi muốn truy vấn cơ sở dữ liệu (thông qua EF / NHibernate / ORM khác) không?

Bạn có thể sử dụng bất cứ phương pháp nào đang chờ bạn có sẵn. Ngay bây giờ hầu hết các cầu thủ lớn đều hỗ trợ async, nhưng có một số ít thì không. Nếu ORM của bạn không hỗ trợ async, thì đừng cố gắng bọc nó Task.Runhoặc bất cứ thứ gì tương tự (xem bên trên).

Lưu ý rằng tôi đã nói "bạn có thể sử dụng". Nếu bạn đang nói về ASP.NET MVC với một phụ trợ cơ sở dữ liệu, thì bạn (gần như chắc chắn) sẽ không nhận được bất kỳ lợi ích về khả năng mở rộng nào async. Điều này là do IIS có thể xử lý các yêu cầu đồng thời hơn nhiều so với một phiên bản duy nhất của máy chủ SQL (hoặc RDBMS cổ điển khác). Tuy nhiên, nếu phụ trợ của bạn hiện đại hơn - cụm máy chủ SQL, Azure SQL, NoQuery, v.v. - và phụ trợ của bạn có thể mở rộng và nút cổ chai khả năng mở rộng của bạn là IIS, thì bạn có thể nhận được lợi ích về khả năng mở rộng async.

Câu hỏi thứ ba - Bao nhiêu lần tôi có thể sử dụng các từ khóa đang chờ để truy vấn cơ sở dữ liệu không đồng bộ trong MỘT phương thức hành động?

Như nhiều như bạn muốn. Tuy nhiên, lưu ý rằng nhiều ORM có quy tắc một thao tác cho mỗi kết nối. Cụ thể, EF chỉ cho phép một hoạt động trên mỗi DbContext; điều này đúng cho dù hoạt động là đồng bộ hay không đồng bộ.

Ngoài ra, hãy ghi nhớ khả năng mở rộng của phụ trợ của bạn một lần nữa. Nếu bạn gặp một phiên bản duy nhất của SQL Server và IIS của bạn đã có khả năng giữ cho SQLServer hoạt động hết công suất, thì việc nhân đôi hoặc tăng gấp ba áp lực lên SQLServer sẽ không giúp ích gì cho bạn cả.


2
@seeb quy: Khi thực hiện (không đồng bộ thích hợp) I / O, không có chủ đề nào được sử dụng. Tôi có một bài viết blog đi vào chi tiết hơn.
Stephen Cleary

2
IIS và một cá thể SQL Server có thể xử lý bao nhiêu yêu cầu? Làm thế nào tôi có thể tìm thấy những con số này?
MOD

1
@MOD: Nó phụ thuộc vào cấu hình và phần cứng. Cách duy nhất để tìm những con số này là thực hiện kiểm tra tải trên máy chủ của riêng bạn.
Stephen Cleary

1
@Flezcano: Các phương thức thực thi đầy đủ trên CPU. Tức là, họ không làm I / O hoặc chặn.
Stephen Cleary

1
Xin lỗi thưa ngài nhưng SO không chấp nhận câu hỏi 2 câu hỏi này mà tôi đang cố gắng hiểu. Khi tôi đang nghiên cứu cho vấn đề này như thế nào nếu tôi có 1 điểm cuối api web được gọi bởi 1000 người dùng cùng một lúc thì điểm cuối này sẽ có thể đến máy chủ mỗi nghìn yêu cầu này hay tôi nên làm cho nó không đồng bộ để xử lý 1000 yêu cầu?
Học tập-lật đổ-nhầm lẫn

21

sử dụng hành động async ở mọi nơi trong ASP.NET MVC có tốt không?

Như thường lệ trong lập trình, nó phụ thuộc . Luôn có sự đánh đổi khi đi xuống một con đường nhất định.

async-awaittỏa sáng ở những nơi bạn biết bạn sẽ nhận được yêu cầu đồng thời cho dịch vụ của mình và bạn muốn có thể mở rộng quy mô tốt. Làm thế nào để async-awaitgiúp mở rộng quy mô? Trong thực tế là khi bạn gọi một cuộc gọi IO không đồng bộ , chẳng hạn như một cuộc gọi mạng hoặc nhấn vào cơ sở dữ liệu của bạn, luồng hiện tại chịu trách nhiệm thực thi sẽ bị chặn chờ yêu cầu kết thúc. Khi bạn sử dụng async-await, bạn kích hoạt khung để tạo một máy trạng thái cho bạn, đảm bảo rằng sau khi cuộc gọi IO hoàn thành, phương thức của bạn sẽ tiếp tục thực hiện từ nơi nó dừng lại.

Một điều cần lưu ý là máy trạng thái này có một chi phí tinh tế. Làm cho một phương thức không đồng bộ không làm cho nó thực thi nhanh hơn và đó là một yếu tố quan trọng để hiểu và là một quan niệm sai lầm mà nhiều người mắc phải.

Một điều khác cần xem xét khi sử dụng async-awaitlà thực tế là nó không đồng bộ theo mọi cách , có nghĩa là bạn sẽ thấy async thâm nhập vào toàn bộ ngăn xếp cuộc gọi của bạn, từ đầu đến mông. Điều này có nghĩa rằng nếu bạn muốn để lộ của API đồng bộ, bạn thường sẽ thấy mình sao chép một số tiền nhất định của mã, như asyncđồng bộ hóa không pha trộn rất tốt.

Tôi có nên sử dụng async / await từ khóa khi tôi muốn truy vấn cơ sở dữ liệu (thông qua EF / NHibernate / ORM khác) không?

Nếu bạn chọn đi xuống con đường sử dụng các cuộc gọi IO không đồng bộ, thì có, async-awaitsẽ là một lựa chọn tốt, vì ngày càng nhiều nhà cung cấp cơ sở dữ liệu hiện đại trưng ra phương thức async thực hiện TAP (Mẫu không đồng bộ tác vụ).

Bao nhiêu lần tôi có thể sử dụng các từ khóa đang chờ để truy vấn cơ sở dữ liệu không đồng bộ trong MỘT phương thức hành động?

Nhiều như bạn muốn, miễn là bạn tuân theo các quy tắc được cung cấp bởi nhà cung cấp cơ sở dữ liệu của bạn. Không có giới hạn đối với số lượng cuộc gọi không đồng bộ bạn có thể thực hiện. Nếu bạn có các truy vấn độc lập với nhau và có thể được thực hiện đồng thời, bạn có thể quay một tác vụ mới cho mỗi truy vấn và sử dụng await Task.WhenAllđể chờ cả hai hoàn thành.


2
Các cuộc gọi db không đồng bộ không thực thi nhanh hơn nhưng cho phép cùng một máy IIS cung cấp nhiều yêu cầu hơn vì các luồng xử lý luồng không bị lãng phí. Nó cũng làm giảm chi phí CPU vì việc chặn các cuộc gọi thực sự bắt đầu bằng một vòng quay chuyên sâu về CPU trước khi thực sự chặn. Trong các tình huống tải cao, đây có thể là sự khác biệt giữa máy gặp khó khăn hoặc không hoạt động và tái chế
Panagiotis Kanavos

@PanagiotisKanavos Tôi biết, điều đó không rõ ràng từ những gì tôi nói?
Yuval Itzchakov

1
Câu trả lời của bạn không đề cập đến các chi tiết cụ thể của IIS - kịch bản meltdown / tái chế là lý do chính để sử dụng async xuyên suốt. Một người chưa có kinh nghiệm có lẽ sẽ không hiểu điều đó scale out wellcó nghĩa your server won't die under load. Hoặc đó có thể là một trường hợp củaonce burned ...
Panagiotis Kanavos

7

5 xu của tôi:

  1. Sử dụng async/awaitnếu và chỉ khi bạn thực hiện thao tác IO, như DB hoặc dịch vụ web dịch vụ bên ngoài.
  2. Luôn thích các cuộc gọi không đồng bộ với DB.
  3. Mỗi lần bạn truy vấn DB.

PS Có những trường hợp đặc biệt cho điểm 1, nhưng bạn cần hiểu rõ về nội bộ không đồng bộ cho việc này.

Là một lợi thế bổ sung, bạn có thể thực hiện vài cuộc gọi IO song song nếu cần:

Task task1 = FooAsync(); // launch it, but don't wait for result
Task task2 = BarAsync(); // launch bar; now both foo and bar are running
await Task.WhenAll(task1, task2); // this is better in regard to exception handling
// use task1.Result, task2.Result

6

asynchành động giúp tốt nhất khi các hành động thực hiện một số thao tác I \ O với DB hoặc một số cuộc gọi bị ràng buộc mạng trong đó luồng xử lý yêu cầu sẽ bị đình trệ trước khi nhận được câu trả lời từ DB hoặc cuộc gọi bị ràng buộc mạng mà bạn vừa gọi. Tốt nhất bạn nên sử dụng chờ đợi với họ và nó sẽ thực sự cải thiện khả năng phản hồi của ứng dụng của bạn (vì các luồng đầu ra ASP \ \ đầu ra sẽ bị đình trệ trong khi chờ DB hoặc bất kỳ hoạt động nào khác như vậy). Trong tất cả các ứng dụng của tôi bất cứ khi nào nhiều cuộc gọi đến DB rất cần thiết, tôi luôn luôn gói chúng theo phương thức có thể hiểu được và gọi đó bằng awaittừ khóa.


5

Như bạn đã biết, MVC hỗ trợ các bộ điều khiển không đồng bộ và bạn nên tận dụng lợi thế của nó. Trong trường hợp Bộ điều khiển của bạn, thực hiện một thao tác dài, (có thể là I / o dựa trên đĩa hoặc một cuộc gọi mạng đến một dịch vụ từ xa khác), nếu yêu cầu được xử lý đồng bộ, luồng IIS luôn bận rộn. Kết quả là, luồng chỉ chờ hoạt động dài hoàn thành. Nó có thể được sử dụng tốt hơn bằng cách phục vụ các yêu cầu khác trong khi hoạt động được yêu cầu trước đang được tiến hành. Điều này sẽ giúp phục vụ các yêu cầu đồng thời hơn. Dịch vụ web của bạn sẽ có khả năng mở rộng cao và sẽ không dễ dàng gặp phải sự cố C10k . Đó là một ý tưởng tốt để sử dụng async / await cho các truy vấn db. và có, bạn có thể sử dụng chúng nhiều lần nếu bạn thấy phù hợp.

Hãy xem ở đây để tư vấn tuyệt vời.


3

Kinh nghiệm của tôi là ngày nay rất nhiều nhà phát triển sử dụng async/awaitlàm mặc định cho bộ điều khiển.

Đề nghị của tôi sẽ là, chỉ sử dụng nó khi bạn biết nó sẽ giúp bạn .

Lý do là, như Stephen Cleary và những người khác đã đề cập, nó có thể đưa ra các vấn đề về hiệu suất, thay vì giải quyết chúng, và nó sẽ chỉ giúp bạn trong một kịch bản cụ thể:

  • Kiểm soát lưu lượng cao
  • Phụ trợ mở rộng

2

Có tốt không khi sử dụng hành động async ở mọi nơi trong ASP.NET MVC?

Thật tốt khi làm như vậy bất cứ nơi nào bạn có thể sử dụng phương pháp không đồng bộ, đặc biệt là khi bạn gặp vấn đề về hiệu suất ở cấp quy trình công nhân xảy ra đối với các hoạt động tính toán và dữ liệu lớn. Nếu không, không cần vì thử nghiệm đơn vị sẽ cần đúc.

Về các phương thức có thể chờ đợi: tôi có nên sử dụng từ khóa async / await khi tôi muốn truy vấn cơ sở dữ liệu (thông qua EF / NHibernate / ORM khác) không?

Có, tốt hơn là sử dụng async cho bất kỳ hoạt động DB nào có thể để tránh các vấn đề về hiệu năng ở mức quy trình công nhân. Lưu ý rằng EF đã tạo ra nhiều lựa chọn thay thế không đồng bộ cho hầu hết các hoạt động, chẳng hạn như:

.ToListAsync()
.FirstOrDefaultAsync()
.SaveChangesAsync()
.FindAsync()

Bao nhiêu lần tôi có thể sử dụng các từ khóa đang chờ để truy vấn cơ sở dữ liệu không đồng bộ trong một phương thức hành động?

Bầu trời là giới hạn


Logic nghiệp vụ cho hầu hết các ứng dụng web thường cần các hoạt động tuần tự cao, do đó, việc chuyển đổi giữa các quy trình song song trong hầu hết các trường hợp chỉ khả thi ở cấp cao nhất của quản lý quy trình yêu cầu / công nhân web, dù sao không xử lý trực tiếp các yêu cầu cơ sở dữ liệu. Tôi thực sự muốn xem một số ví dụ trong thế giới thực của một ứng dụng web điển hình trong đó thực sự là một ý tưởng tốt để xử lý các truy vấn DB theo cách không đồng bộ.
JustAMartin
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.