Chờ đợi một nhiệm vụ đã hoàn thành giống như nhiệm vụ.


117

Tôi hiện đang đọc " Concurrency in C # Cookbook " của Stephen Cleary và tôi nhận thấy kỹ thuật sau:

var completedTask = await Task.WhenAny(downloadTask, timeoutTask);  
if (completedTask == timeoutTask)  
  return null;  
return await downloadTask;  

downloadTasklà một cuộc gọi đến httpclient.GetStringAsynctimeoutTaskđang thực thi Task.Delay.

Trong trường hợp nó không hết thời gian chờ, thì downloadTasknó đã được hoàn thành. Tại sao lại cần phải chờ đợi lần thứ hai thay vì quay lại downloadTask.Result, vì nhiệm vụ đã được hoàn thành?


3
Ở đây còn thiếu một chút ngữ cảnh và trừ khi mọi người dễ dàng truy cập vào cuốn sách, bạn sẽ cần đưa nó vào. Là gì downloadTaskvà là timeoutTaskgì? Họ làm gì?
Mike Perrenoud

7
Tôi không thấy kiểm tra thực tế để hoàn thành thành công ở đây. Tác vụ rất có thể bị lỗi và trong trường hợp đó, hành vi sẽ khác ( AggregateExceptionvới Resultngoại lệ đầu tiên so ExceptionDispatchInfovới với với await). Thảo luận chi tiết hơn trong "Task Xử lý ngoại lệ trong .NET 4.5" Stephen Toub của: blogs.msdn.com/b/pfxteam/archive/2011/09/28/... )
Kirill Shlenskiy

bạn nên làm điều này một câu trả lời @KirillShlenskiy
Carsten

@MichaelPerrenoud Bạn nói đúng, cảm ơn bạn đã chú ý, tôi sẽ chỉnh sửa câu hỏi.
julio.

Câu trả lời:


160

Đã có một số câu trả lời / nhận xét hay ở đây, nhưng chỉ để kêu ...

Có hai lý do tại sao tôi thích awaithơn Result(hoặc Wait). Đầu tiên là việc xử lý lỗi khác nhau; awaitkhông bọc ngoại lệ trong một AggregateException. Lý tưởng nhất là mã không đồng bộ không bao giờ phải xử lý AggregateExceptiongì cả, trừ khi nó muốn cụ thể .

Lý do thứ hai là tinh tế hơn một chút. Như tôi mô tả trên blog của mình (và trong sách), Result/ Waitcó thể gây ra bế tắcthậm chí có thể gây ra bế tắc tinh vi hơn khi được sử dụng trong một asyncphương pháp . Vì vậy, khi tôi đang đọc qua mã và tôi thấy Resulthoặc Wait, đó là một cờ cảnh báo ngay lập tức. Dấu Result/ Waitchỉ đúng nếu bạn hoàn toàn chắc chắn rằng nhiệm vụ đã được hoàn thành. Điều này không chỉ khó nhìn thấy trong nháy mắt (trong mã trong thế giới thực), mà còn khó thay đổi hơn đối với các thay đổi mã.

Đó không phải là để nói rằng Result/ Waitnên không bao giờ được sử dụng. Tôi tuân theo các nguyên tắc sau trong mã của riêng mình:

  1. Mã không đồng bộ trong một ứng dụng chỉ có thể sử dụng await.
  2. Mã tiện ích không đồng bộ (trong thư viện) đôi khi có thể sử dụng Result/ Waitnếu mã thực sự yêu cầu nó. Cách sử dụng như vậy có lẽ nên có nhận xét.
  3. Mã nhiệm vụ song song có thể sử dụng ResultWait.

Lưu ý rằng (1) cho đến nay là trường hợp phổ biến, do đó tôi có xu hướng sử dụng awaitở mọi nơi và coi các trường hợp khác là ngoại lệ đối với quy tắc chung.


Chúng tôi đã gặp phải bế tắc khi sử dụng 'result' thay vì 'await' trong các dự án của mình. phần lộn xộn không có lỗi biên dịch và mã của bạn trở nên không ổn định sau một thời gian.
Ahmad Mousavi

@Stephen bạn sẽ vui lòng giải thích cho tôi tại sao "Lý tưởng nhất, mã không đồng bộ nên không bao giờ phải đối phó với AggregateException ở tất cả, trừ khi nó đặc biệt muốn"
vcRobe

4
@vcRobe Vì awaitngăn AggregateExceptiontrình bao bọc. AggregateExceptionđược thiết kế để lập trình song song, không phải lập trình không đồng bộ.
Stephen Cleary

2
> "Chờ chỉ đúng nếu bạn hoàn toàn chắc chắn rằng nhiệm vụ đã được hoàn thành." .... Vậy tại sao nó được gọi là Chờ?
Ryan The Leach

4
@RyanTheLeach: Mục đích ban đầu Waitlà tham gia vào các phiên bản Dynamic Task Parallelism Task . Sử dụng nó để chờ các Tasktrường hợp không đồng bộ là rất nguy hiểm. Microsoft đã cân nhắc việc giới thiệu một loại "Promise" mới, nhưng đã chọn sử dụng loại hiện có Taskđể thay thế; Sự đánh đổi của việc sử dụng lại Taskloại hiện có cho các tác vụ không đồng bộ là bạn sẽ có một số API mà đơn giản là không nên sử dụng trong mã không đồng bộ.
Stephen Cleary

12

Điều này có ý nghĩa nếu timeoutTasklà một sản phẩm của nó Task.Delay, mà tôi tin nó là gì trong cuốn sách.

Task.WhenAnytrả về Task<Task>, trong đó tác vụ bên trong là một trong những tác vụ bạn đã chuyển làm đối số. Nó có thể được viết lại như thế này:

Task<Task> anyTask = Task.WhenAny(downloadTask, timeoutTask);
await anyTask;
if (anyTask.Result == timeoutTask)  
  return null;  
return downloadTask.Result; 

Trong cả hai trường hợp, vì downloadTaskđã hoàn thành nên có một sự khác biệt rất nhỏ giữa return await downloadTaskreturn downloadTask.Result. Đó là ở chỗ cái thứ hai sẽ ném AggregateExceptioncái bao bọc bất kỳ ngoại lệ ban đầu nào, như được chỉ ra bởi @KirillShlenskiy trong các nhận xét. Trước đây sẽ chỉ ném lại ngoại lệ ban đầu.

Trong cả hai trường hợp, bất cứ nơi nào bạn xử lý các ngoại lệ, bạn nên kiểm tra AggregateExceptionvà kiểm tra các ngoại lệ bên trong của nó, để tìm ra nguyên nhân của lỗi.

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.