等待一个完成的任务相同的task.Result?

我目前正在阅读Stephen Cleary的“C#Cookbook中的并发”,并且我注意到了以下技术:

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

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

如果没有超时,那么downloadTask已经完成。 为什么有必要做一秒钟的等待,而不是返回downloadTask.Result,鉴于任务已经完成?

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

有两个原因,我更喜欢await Result (或Wait )。 首先是error handling是不同的; await不会在AggregateException包装exception。 理想情况下,asynchronous代码根本不需要处理AggregateException ,除非特别需要。

第二个原因是更微妙一点。 正如我在我的博客(和本书)中所描述的那样, Result / Wait会导致死锁 ,并且在async方法中使用时会导致更微妙的死锁 。 所以,当我通过代码阅读,并看到一个ResultWait ,这是一个即时的警告标志。 如果您完全确定任务已完成, Result / Wait才是正确的。 不仅难以一眼就看出(在真实世界的代码中),而且编码变化也更加脆弱。

这并不是说不应该使用Result / Wait 。 我在我自己的代码中遵循这些准则:

  1. 应用程序中的asynchronous代码只能使用await
  2. 如果代码真的需要,asynchronous实用程序代码(在库中)偶尔会使用Result / Wait 。 这种用法应该可能有意见。
  3. 并行任务代码可以使用ResultWait

请注意,(1)是常见的情况,因此,我倾向于使用,并将其他情况视为一般规则的例外情况。

这是有道理的,如果timeoutTasktimeoutTask的产品,我相信这是书中的内容。

Task.WhenAny返回Task<Task> ,其中内部任务是您作为parameter passing的任务之一。 它可以像这样重写:

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

在这两种情况下,因为downloadTask已经完成,所以在return await downloadTaskreturn downloadTask.Result之间有一个很小的区别。 这是因为后者将抛出AggregateException ,它包装了任何原始exception,正如@KirillShlenskiy在注释中指出的那样。 前者会重新抛出原来的exception。

在任何情况下,无论您处理exception,都应该检查AggregateException及其内部exception,以找出错误的原因。