【问题标题】:Using await on async method that returns only Task在仅返回 Task 的异步方法上使用 await
【发布时间】:2021-05-11 05:41:28
【问题描述】:

在我的 ASP.NET Core 应用程序中,我创建了几个(十几个)方法

public async Task DoStuff() 
{ 
   // ...
}

所以,没有返回类型。

后来,我意识到我不小心忘记在调用者方法中到处都包含 await (显然没有 async 关键字,因为异步“僵尸病毒”还没有传播到那么远)。

在执行期间,没有任何不良后果。

问题是,当这种情况发生时,Visual Studio 会生成没有警告消息,我问自己,实际上是否存在在这种情况下忽略等待的任何危险?我知道 await 应该自然地应用于每个 async 方法,但是当调用者实际上没有要使用的返回值时,我真的不明白这背后的原因。也许有捕捉异常的东西?

我还没有找到任何明确的答案,因为一般性陈述是“简单地包含等待”。不管你信不信,我对这个 async/await 还比较陌生,它时不时地反复咬我。

【问题讨论】:

  • So, no return type - 不,返回类型是 Task,而不是例如async void.
  • @GSerg 抱歉,我的意思是在 async/await 术语的上下文中,因为由 Task 包装的方法不会产生返回类型。我猜应该按照我的措辞工作。
  • @GSerg 感谢您的建议,我看到斯蒂芬通过查看对#1 提供的解释部分回答了我的问题。考虑到这一点,您是否可以评论以下内容:如果我自己处理上述方法中的异常(未正确等待的异常),那么我们可以说一切都很好吗?调用者不会知道异常,是的,但它们不会完全丢失。
  • 是否可以在问题中包含比DoStuff 方法更现实的示例?如果这个方法有一个名称来表明它的作用,那么回答这个问题会更容易。此外,通过显示您调用它的方式和位置将帮助我们估计省略 await 是否有后果,以及什么样的后果。顺便说一句,根据guidelinesDoStuff 方法的名称应该是DoStuffAsync

标签: c# .net asynchronous .net-core async-await


【解决方案1】:

在执行期间,没有任何不良后果。

我不同意。生成的代码很危险。 ASP.NET pre-Core 能够检测到类似情况并引发异常(“异步模块或处理程序已完成,而异步操作仍处于挂起状态”)。由于技术原因,ASP.NET Core 无法检测到这种情况,因此您不会收到“安全网”异常,但情况本身仍然很糟糕。

问题是,当这种情况发生时,Visual Studio 也不会生成警告消息

你没有收到CS4014 ("Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the await operator to the result of the call.")

在这种情况下忽略等待实际上存在任何危险吗?我知道 await 应该自然地应用于每个 async 方法,但是当调用者实际上没有要使用的返回值时,我真的不明白这背后的原因。也许有捕捉异常的东西?

是的,有危险。 Task(即使没有结果类型)用于两件事:让调用者知道操作何时完成,以及让调用者检测该操作的异常。

所以,一个问题是异常被默默吞没。更具体地说,async 方法中的异常被async 状态机捕获并放置在返回的Task 上,然后被忽略。

如果我自己处理上述方法中的异常(未正确等待的),那么我们可以说一切都很好吗?

不,因为另一个问题仍然存在:调用者不知道异步操作何时完成。在 ASP.NET 中了解这一点尤为重要,因为在操作完成之前不应发送结果。 ASP.NET 上的任何“即发即弃”代码都存在于请求/响应生命周期之外;即,它是请求外部代码。

我在我的博客上详细介绍了why request-extrinsic code is dangerous。总之,您的 ASP.NET 处理程序可能过早完成,在这种情况下,请求外部代码可能会“丢失”。至少,在发送响应时,它所做的任何事情都不会完成;在定期关闭的情况下(例如,滚动升级),它可能根本无法完成

【讨论】:

  • 谢谢你的回答,斯蒂芬。我追踪了所有缺少等待的情况。正如我所说,没有任何警告,因为那些只返回普通任务的异步是从同步方法中调用的。尽管如此,我还是有一种情况,我无法等待调用的异步 - 这是 WCF IClientMessageInspector 的方法,它们包含一个“ref”作为参数,例如void AfterReceiveReply(参考消息回复,对象相关状态)。调用的异步不是业务关键的东西(它是一个记录器),但您是否提出任何解决方法?
  • @NamespaceNotFound:对于记录器,我建议有一个单独的专用记录线程,并使所有记录方法同步添加到由该线程处理的内存队列中。因此,日志记录不需要异步。
猜你喜欢
  • 2019-07-22
  • 2016-10-28
  • 1970-01-01
  • 1970-01-01
  • 2020-04-12
  • 2012-08-09
  • 1970-01-01
  • 1970-01-01
  • 2022-06-21
相关资源
最近更新 更多