等待与task.Result相同的已完成任务?

jul*_*o.g 107 c# asynchronous task async-await

我正在阅读Stephen Cleary撰写的"C#Cookbook中的并发",我注意到以下技巧:

var completedTask = await Task.WhenAny(downloadTask, timeoutTask);  
if (completedTask == timeoutTask)  
  return null;  
return await downloadTask;  
Run Code Online (Sandbox Code Playgroud)

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

如果它没有超时,则downloadTask已经完成.为什么有必要做第二次等待而不是返回downloadTask.Result,因为任务已经完成了?

Ste*_*ary 150

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

有两个原因为什么我喜欢awaitResult(或Wait).首先是错误处理是不同的; await不会将异常包装在一个AggregateException.理想情况下,异步代码根本不应该处理AggregateException,除非它特别想要.

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

这并不是说Result/ Wait应该永远不会被使用.我在自己的代码中遵循这些指南:

  1. 应用程序中的异步代码只能使用await.
  2. 异步实用程序代码(在库中)偶尔可以使用Result/ Wait如果代码真的需要它.这种用法应该有评论.
  3. 并行任务代码可以使用ResultWait.

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

  • @vcRobe因为`await`阻止了`AggregateException`包装器.`AggregateException`是为并行编程而设计的,而不是异步编程. (4认同)
  • @RyanTheLeach:`Wait`的最初目的是加入[Dynamic Task Parallelism](https://msdn.microsoft.com/en-us/library/gg663539.aspx)`Task`实例.使用它来等待异步`Task`实例是危险的.微软考虑引入一种新的"Promise"类型,但选择使用现有的`Task`代替; 重用现有`Task`类型用于异步任务的权衡是你最终会得到几个不应该在异步代码中使用的API. (4认同)
  • >"只有在你完全确定任务已经完成时,等待才是正确的." ....那为什么叫做等待? (2认同)

nos*_*tio 11

如果它timeoutTask是一种产品,这是有道理的Task.Delay,我相信它在书中的含义.

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

Task<Task> anyTask = Task.WhenAny(downloadTask, timeoutTask);
await anyTask;
if (anyTask.Result == timeoutTask)  
  return null;  
return downloadTask.Result; 
Run Code Online (Sandbox Code Playgroud)

在任何一种情况下,由于downloadTask已经完成,因此return await downloadTask和之间存在非常小的差异return downloadTask.Result.AggregateException正如@KirillShlenskiy在评论中所指出的那样,后者将抛出任何原始异常.前者只会重新抛出原始异常.

在任何一种情况下,无论何处处理异常,都应该检查AggregateException及其内部异常,以找出错误原因.