Sự khác biệt giữa trả lại khoảng trống và trả lại một nhiệm vụ là gì?


128

Khi xem xét các mẫu CTP Async C # khác nhau, tôi thấy một số hàm async trả về voidvà các hàm khác trả về giá trị không chung Task. Tôi có thể thấy lý do tại sao trả về a Task<MyType>là hữu ích để trả lại dữ liệu cho người gọi khi hoạt động async hoàn thành, nhưng các chức năng mà tôi đã thấy có kiểu trả về Taskkhông bao giờ trả lại bất kỳ dữ liệu nào. Tại sao không trở về void?

Câu trả lời:


214

Câu trả lời của SLaks và Killercam là tốt; Tôi nghĩ rằng tôi chỉ cần thêm một chút bối cảnh.

Câu hỏi đầu tiên của bạn về cơ bản là về những phương pháp có thể được đánh dấu async.

Một phương thức được đánh dấu là asynccó thể trả về void, Taskhoặc Task<T>. Sự khác biệt giữa chúng là gì?

Một Task<T>phương thức async trở lại có thể được chờ đợi và khi tác vụ hoàn thành, nó sẽ tạo ra một chữ T.

Một Taskphương thức async trở lại có thể được chờ đợi và khi tác vụ hoàn thành, việc tiếp tục tác vụ được lên lịch để chạy.

Một voidphương thức async trở lại không thể được chờ đợi; đó là một phương pháp "lửa và quên". Nó hoạt động không đồng bộ và bạn không có cách nào để biết khi nào nó được thực hiện. Điều này là nhiều hơn một chút kỳ lạ; như SLaks nói, thông thường bạn sẽ chỉ làm điều đó khi thực hiện một trình xử lý sự kiện không đồng bộ. Sự kiện cháy, người xử lý thực hiện; không ai sẽ "chờ đợi" nhiệm vụ được trả về bởi trình xử lý sự kiện vì các trình xử lý sự kiện không trả lại các tác vụ và ngay cả khi chúng đã thực hiện, mã nào sẽ sử dụng Tác vụ cho việc gì? Đó thường không phải là mã người dùng chuyển điều khiển đến trình xử lý ở vị trí đầu tiên.

Câu hỏi thứ hai của bạn, trong một bình luận, về cơ bản là về những gì có thể được awaited:

Những loại phương pháp có thể được awaited? Một phương thức void-return có thể được awaited không?

Không, một phương thức trả về khoảng trống không thể được chờ đợi. Trình biên dịch chuyển await M()thành một cuộc gọi đến M().GetAwaiter(), trong đó GetAwaitercó thể là một phương thức thể hiện hoặc một phương thức mở rộng. Giá trị được chờ đợi phải là một giá trị mà bạn có thể nhận được một người phục vụ; rõ ràng một phương thức trả về khoảng trống không tạo ra một giá trị mà từ đó bạn có thể nhận được một người phục vụ.

Taskphương pháp trả lại có thể tạo ra các giá trị chờ đợi. Chúng tôi dự đoán rằng các bên thứ ba sẽ muốn tạo ra các triển khai các Taskđối tượng giống như của riêng họ có thể được chờ đợi và bạn sẽ có thể chờ đợi họ. Tuy nhiên, bạn sẽ không được phép khai báo asynccác phương thức trả về bất cứ thứ gì ngoài void, Taskhoặc Task<T>.

(CẬP NHẬT: Câu cuối cùng của tôi có thể bị làm sai lệch bởi một phiên bản tương lai của C #; có một đề xuất cho phép các kiểu trả về khác với các loại nhiệm vụ cho các phương thức không đồng bộ.)

(CẬP NHẬT: Tính năng được đề cập ở trên đã đưa nó vào C # 7.)


7
+1 Tôi nghĩ điều duy nhất còn thiếu là sự khác biệt trong cách xử lý các ngoại lệ trong các phương thức async trả về void.
João Angelo

10
@JamesCadd: Giả sử một số công việc không đồng bộ ném ngoại lệ. Ai bắt được nó? Mã bắt đầu tác vụ không đồng bộ không còn trên ngăn xếp nữa - nó thậm chí có thể không nằm trên cùng một luồng - và các trường hợp ngoại lệ cho rằng tất cả các khối bắt / cuối cùng nằm trên ngăn xếp . Vậy bạn làm gì? Chúng tôi lưu trữ thông tin ngoại lệ trong Tác vụ để bạn có thể kiểm tra thông tin sau. Nhưng nếu phương thức là void return thì không có tác vụ nào có sẵn cho mã người dùng. Làm thế nào chính xác chúng ta đối phó với tình huống đó là một vấn đề gây tranh cãi và tôi không nhớ lại tại thời điểm này những gì chúng ta quyết định.
Eric Lippert

8
Tôi thực sự đã hỏi câu hỏi này của Stephen Toub tại BUILD. Trong .NET 4.0 không được quan sát, các ngoại lệ chưa được xử lý trong Nhiệm vụ cuối cùng sẽ phá vỡ quy trình một khi TPL phát hiện ra chúng không được quan sát. Trong 4.5 họ đã thay đổi hành vi mặc định để các trường hợp ngoại lệ không được quan sát sẽ vẫn được báo cáo thông qua sự kiện TaskScheduler :: UnobservedTaskException, nhưng sẽ không làm hỏng quá trình. Nếu bạn muốn hành vi 4.0 cũ, bạn có thể chọn tham gia lại với <runtime> <throwUnobservedTaskExceptions enable = "true" /> </ runtime>. Nhiều khả năng thay đổi đã được thực hiện chính xác để hỗ trợ phương pháp chữa cháy và quên cho các phương thức void async.
vẽ Marsh Marsh

4
async voidcác phương thức đưa ra ngoại lệ của chúng đối với SynchronizationContexthoạt động tại thời điểm chúng bắt đầu thực thi. Điều này tương tự như hành vi của các trình xử lý sự kiện (đồng bộ). @DrewMarsh: UnobservedTaskExceptioncài đặt và thời gian chạy chỉ áp dụng cho các phương thức Nhiệm vụ async và "quên" , không phải async voidphương thức.
Stephen Cleary

1
Link trích dẫn để biết xử lý async ngoại lệ: blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx#11
Luke Puplett

23

Trong trường hợp người gọi muốn chờ vào nhiệm vụ hoặc thêm phần tiếp theo.

Trên thực tế, lý do duy nhất để quay lại voidlà nếu bạn không thể quay lại Taskvì bạn đang viết một trình xử lý sự kiện.


Tôi nghĩ rằng cũng có thể chờ đợi các phương thức trả về loại khoảng trống - bạn có thể giải thích một chút không?
James Cadd

1
Không, bạn không thể. Nếu phương thức trả về void, bạn không có cách nào nhận được tác vụ mà nó tạo ra. (Trên thực tế, tôi không chắc liệu nó thậm chí có tạo ra hay không Task)
SLaks

18

Các phương thức trở lại TaskTask<T>có thể ghép lại - có nghĩa là bạn có thể đặt awaitchúng bên trong một asyncphương thức.

asynccác phương thức trả về voidkhông thể kết hợp được, nhưng chúng có hai thuộc tính quan trọng khác:

  1. Chúng có thể được sử dụng như xử lý sự kiện.
  2. Chúng đại diện cho một hoạt động không đồng bộ "cấp cao nhất".

Điểm thứ hai rất quan trọng khi bạn xử lý một bối cảnh duy trì một số lượng hoạt động không đồng bộ nổi bật.

Bối cảnh ASP.NET là một trong những bối cảnh như vậy; nếu bạn sử dụng Taskcác phương thức async mà không chờ chúng từ voidphương thức async , thì yêu cầu ASP.NET sẽ được hoàn thành quá sớm.

Một bối cảnh khác là AsyncContexttôi đã viết để kiểm tra đơn vị (có sẵn ở đây ) - AsyncContext.Runphương thức theo dõi số lượng hoạt động nổi bật và trả về khi nó bằng không.


12

Loại Task<T>là loại công việc của Thư viện song song nhiệm vụ (TPL), nó đại diện cho khái niệm "một số công việc / công việc sẽ tạo ra kết quả của loại Ttrong tương lai". Khái niệm "công việc sẽ hoàn thành trong tương lai nhưng không trả lại kết quả" được biểu thị bằng loại Nhiệm vụ không chung chung.

Chính xác kết quả của loại Tsẽ được tạo ra như thế nào và chi tiết thực hiện của một nhiệm vụ cụ thể; công việc có thể được chuyển sang một quy trình khác trên máy cục bộ, cho một luồng khác, v.v. Các tác vụ TPL thường được đưa ra cho các luồng công nhân từ một nhóm luồng trong quy trình hiện tại, nhưng chi tiết triển khai đó không phải là cơ bản cho Task<T>loại; thay vì a Task<T>có thể đại diện cho bất kỳ hoạt động có độ trễ cao nào tạo ra a T.

Dựa trên nhận xét của bạn ở trên:

Các await phương tiện khái niệm "đánh giá biểu thức này để có được một đối tượng đại diện cho công việc mà ý chí trong tương lai sản xuất một kết quả. Đăng ký thời gian còn lại của phương pháp hiện tại như là gọi lại gắn liền với việc tiếp tục công việc đó. Một khi nhiệm vụ được sản xuất và các cuộc gọi trở lại được đăng ký, ngay lập tức trả lại quyền kiểm soát cho người gọi của tôi ". Điều này trái ngược / trái ngược với một cuộc gọi phương thức thông thường, có nghĩa là "nhớ những gì bạn đang làm, chạy phương thức này cho đến khi hoàn thành xong và sau đó chọn nơi bạn rời đi, bây giờ biết kết quả của phương thức".


Chỉnh sửa: Tôi nên trích dẫn bài viết của Eric Lippert vào tháng 10 năm 2011 Tạp chí MSDN vì đây là một trợ giúp tuyệt vời cho tôi trong việc hiểu nội dung này ngay từ đầu.

Đối với tải thêm thông tin và whitepages xem tại đây .

Tôi hy vọng đây là một số giúp đỡ.

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.