等待一个与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
?
这里已经有一些很好的答案/评论,但是只是为了插话…
我更喜欢await
而不是Result
(或Wait
)有两个原因。首先,错误处理是不同的;await
不将异常包装在AggregateException
中。理想情况下,异步代码不应该处理AggregateException
,除非它特别希望处理。
第二个原因更微妙一些。正如我在我的博客(和书中)所描述的那样,Result
/Wait
可能导致死锁,并且在async
方法中使用时可能导致更微妙的死锁。所以,当我阅读代码时,我看到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
及其内部异常,以找到错误的原因。