【问题标题】:Await on a completed task same as task.Result?等待与 task.Result 相同的已完成任务?
【发布时间】:2014-07-08 03:13:13
【问题描述】:

我目前正在阅读 Stephen Cleary 的“Concurrency in 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

【问题讨论】:

  • 我在这里没有看到成功完成的实际检查。该任务很可能会出错,在这种情况下,行为不同(AggregateExceptionResult 与第一个异常通过ExceptionDispatchInfoawait)。在 Stephen Toub 的“.NET 4.5 中的任务异常处理”中有更详细的讨论:blogs.msdn.com/b/pfxteam/archive/2011/09/28/…)
  • 你应该把这个作为答案@KirillShlenskiy
  • @MichaelPerrenoud 你说得对,谢谢你的注意,我会编辑问题。

标签: c# asynchronous async-await task


【解决方案1】:

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

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

第二个原因有点微妙。正如我在我的博客(和书中)、Result/Wait can cause deadlockscan cause even more subtle deadlocks when used in an async method 中所描述的那样。因此,当我阅读代码并看到ResultWait 时,这是一个即时警告标志。 Result/Wait 只有在您绝对确定任务已经完成时才是正确的。这不仅很难一目了然(在实际代码中),而且代码更改也更脆弱。

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

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

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

【讨论】:

  • 我们在项目中使用 'result' 而不是 'await' 遇到了死锁。搞砸的部分没有编译错误,并且您的代码在一段时间后变得不稳定。
  • @vcRobe 因为await 阻止了AggregateException 包装器。 AggregateException 是为并行编程设计的,而不是异步编程。
  • > "只有当您完全确定任务已经完成时,等待才是正确的。" ....那为什么叫Wait呢?
  • @RyanTheLeach:Wait 的最初目的是加入 Dynamic Task Parallelism Task 实例。使用它来等待异步 Task 实例是危险的。 Microsoft 考虑引入新的“Promise”类型,但选择使用现有的Task;将现有的 Task 类型重用于异步任务的权衡是,您最终会得到几个根本不应该在异步代码中使用的 API。
  • @PatrickTucci:“并行任务代码”正在使用动态任务并行性,即在线程池线程上运行任务并将它们链接在一起。与async/awaitWhenAll 无关。在你的情况下,听起来你有异步并发,所以await 是合适的工具。
【解决方案2】:

如果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 在 cmets 中指出的那样,后者将抛出 AggregateException 包装任何原始异常。前者只会重新抛出原始异常。

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

【讨论】:

    猜你喜欢
    • 2012-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-14
    • 2013-12-18
    相关资源
    最近更新 更多