我目前正在阅读 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
?
AggregateException
与 Result
对比第一个异常通过 ExceptionDispatchInfo
与 await
)。在 Stephen Toub 的“.NET 4.5 中的任务异常处理”中有更详细的讨论:blogs.msdn.com/b/pfxteam/archive/2011/09/28/…)
这里已经有一些很好的答案/评论,但只是为了插话......
我更喜欢 await
而不是 Result
(或 Wait
)的原因有两个。首先是错误处理不同; await
不会将异常包装在 AggregateException
中。理想情况下,异步代码根本不需要处理 AggregateException
,除非它特别想要。
第二个原因更微妙一些。正如我在我的博客(和书中)、Result
/Wait
can cause deadlocks 和 can cause even more subtle deadlocks when used in an async
method 中所描述的那样。因此,当我阅读代码并看到 Result
或 Wait
时,这是一个立即警告标志。只有当您绝对确定任务已经完成时,Result
/Wait
才是正确的。这不仅很难一目了然(在现实世界的代码中),而且对代码更改也更加脆弱。
这并不是说应该从不使用 Result
/Wait
。我在自己的代码中遵循这些准则:
应用程序中的异步代码只能使用 await。如果代码确实需要,异步实用程序代码(在库中)偶尔可以使用 Result/Wait。这样的用法大概应该有注释。并行任务代码可以使用 Result 和 Wait。
请注意,(1) 是迄今为止常见的情况,因此我倾向于在任何地方使用 await
并将其他情况视为一般规则的例外情况。
如果 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;
在任何一种情况下,因为 downloadTask
已经完成,所以 return await downloadTask
和 return downloadTask.Result
之间存在非常小的差异。正如@KirillShlenskiy 在评论中指出的那样,后者将抛出包含任何原始异常的 AggregateException
。前者只会重新抛出原始异常。
在任何一种情况下,无论您在哪里处理异常,都应该检查 AggregateException
及其内部异常,以找出错误的原因。
await
阻止了AggregateException
包装器。AggregateException
是为并行编程设计的,而不是异步编程。Wait
的最初目的是加入 Dynamic Task ParallelismTask
个实例。使用它来等待异步Task
实例是危险的。 Microsoft 考虑引入新的“Promise”类型,但选择使用现有的Task
;为异步任务重用现有Task
类型的权衡是,您最终会得到几个根本不应该在异步代码中使用的 API。Parallel task code can use Result and Wait
时,您是什么意思?假设我有两个Task
并使用Task.WhenAll
等待它们完成。我应该使用await
来获得结果吗?还是Result
可以接受?async
/await
或WhenAll
无关。在您的情况下,听起来您有异步并发,因此await
是合适的工具。