等待一个与task. result相同的已完成任务

本文关键字:result 完成任务 task 一个 等待 | 更新日期: 2023-09-27 18:11:47

我目前正在阅读Stephen Cleary的 c# Concurrency in Cookbook,我注意到以下技巧:

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

downloadTask是对httpclient.GetStringAsync的调用,timeoutTask正在执行Task.Delay

如果它没有超时,那么downloadTask已经完成。既然任务已经完成,为什么有必要做第二个await而不是返回downloadTask.Result ?

等待一个与task. result相同的已完成任务

这里已经有一些很好的答案/评论,但是只是为了插话…

我更喜欢await而不是Result(或Wait)有两个原因。首先,错误处理是不同的;await不将异常包装在AggregateException中。理想情况下,异步代码不应该处理AggregateException,除非它特别希望处理

第二个原因更微妙一些。正如我在我的博客(和书中)所描述的那样,Result/Wait可能导致死锁,并且在async方法中使用时可能导致更微妙的死锁。所以,当我阅读代码时,我看到ResultWait,这是一个立即的警告标志。Result/Wait只有在绝对确定任务已经完成时才正确。这不仅很难一眼看出(在实际代码中),而且对代码更改也更脆弱。

这并不是说Result/Wait不应该使用。我在自己的代码中遵循这些准则:
  1. 应用程序中的异步代码只能使用await
  2. 异步实用程序代码(在库中)可以偶尔使用Result/Wait,如果代码真的调用它。这样的用法应该有注释。
  3. 并行任务代码可以使用ResultWait

请注意(1)是目前为止最常见的情况,因此我倾向于在任何地方使用await,并将其他情况视为一般规则的例外。

如果timeoutTaskTask.Delay的产物,这是有道理的,我相信书上是这样写的。

Task.WhenAny返回Task<Task>,其中内部任务是作为参数传递的任务之一。可以这样重写:

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

在这两种情况下,因为downloadTask已经完成,所以return await downloadTaskreturn downloadTask.Result之间的差异非常小。正如@KirillShlenskiy在评论中指出的那样,后者会抛出AggregateException,它包装了任何原始异常。前者只会重新抛出原来的异常。

无论哪种情况,无论在哪里处理异常,都应该检查AggregateException及其内部异常,以找到错误的原因。