【问题标题】:Is it OK to declare an async method as returning void to silence the CS4014 warning?可以将异步方法声明为返回 void 以使 CS4014 警告静音吗?
【发布时间】:2015-12-22 07:59:31
【问题描述】:

Visual Studio 会针对此代码发出警告(“因为未等待此调用,所以在调用完成之前继续执行当前方法”)。

static void Main(string[] args)
{
    FireAndForget(); // <-- Warning CS4014
    // Do something else.
}

static async Task FireAndForget()
{
    // Do something (cannot throw).
}

我的理解是,在这种特殊情况下不等待任务是可以的,因为 FireAndForget 永远不会抛出异常。

我没有使用编译指示禁用警告,而是考虑将 FireAndForget 的返回类型从 Task 更改为 void。这有效地使编译器静音。

static async void FireAndForget() // <-- Task changed to void
{
    // Do something (cannot throw).
}

但是,根据Stephen Cleary,应该避免使用“async void”方法,所以我不太确定该怎么做。

如果方法一开始没有被设计为可等待并且不会抛出异常,是否可以使用“async void”方法?

【问题讨论】:

  • 该方法一开始就不是可等待的 这是什么意思? FireAndForget 究竟做了什么?
  • 为什么这到底是被否决+投票结束?
  • @ken2k 我没有投票结束,但我可以看到这将被视为基于意见的问题,没有客观正确的答案。也许“可以吗”可以改写为“有什么优点和缺点”。
  • @Yuval Itzchakov 只是假设 FireAndForget 做了一些 IO 并负责处理所有错误。更好的是:想象一下 FireAndForget 是一种为 .NET 4.0 编写的方法,它正在被移植到 .NET 4.5。

标签: c# async-await


【解决方案1】:

如果没有设计方法,是否可以使用“async void”方法 首先是可等待的,如果不抛出异常?

虽然这样做可能“可以”,但我仍然鼓励您使用async Task 方法。即使您 100% 确定此方法不会抛出,也不会等待,但您永远不知道它最终会如何被使用。您现在已经完全了解使用 async void 的后果是什么,但如果将来有人可能需要使用它,那么您最好对为什么这个 Task 发表一个很好的评论没有等待,而是采用简单的方法来制作这个void

不要让编译器警告让您担心,我想说的是担心正确性以及这可能对您的代码库产生的影响。

【讨论】:

  • 仅仅因为方法是 'async' 而将 'void' 更改为 'Task' 感觉不对。这就是为什么。 'async' 的唯一目的是解锁在方法体中使用 'await' 的可能性。我的观点是,这意味着“异步”不是该方法的外部合同的一部分。相反,将“无效”更改为“任务”会更改外部合同。
  • @ZunTzu 却暴露了方法的真实操作。当我看到void 返回方法时,我可以安全地假设它是同步的。调用这样一个完成但它的底层操作的方法不会更令人困惑,并且可能对调用者撒谎。
  • 作为一般规则,调用者无法判断我是启动工作线程还是调用 Web 服务。为什么呼叫者需要知道我是否使用“等待”?在我看来,这违反了封装原则。
  • @ZunTzu 调用者对async一无所知,他也不在乎。但是当你暴露一个Task 时,你实际上暴露了一个promise,它告诉调用者你正在执行一些将在未来完成的工作。他不需要知道你如何做你的工作的实施细节。通过公开 void 返回方法,您将无法控制调用代码。
【解决方案2】:

非常很少有真正的即发即弃的操作;也就是说,一个操作:

  • 没有人关心它何时完成。
  • 没有人关心它是否完成。
  • 没有人关心它是否引发异常。

尤其是最后一个;大多数所谓的“即发即弃”操作实际上并不是“即发即弃”,因为如果它不成功,就需要采取一些行动。

也就是说,在某些情况下,真正的“即发即弃”是适用的。

我更喜欢使用async Task 并通过将任务分配给其他未使用的变量来避免编译器警告:

var _ = FireAndForget();

async Task 方法比async void 方法更具可重用性和可测试性。

但是,如果我的团队中的开发人员只是使用 async void 来代替,我不会大吃一惊。

【讨论】:

  • 我很难相信它会如此罕见。如果没有即发即弃的方法,“异步”不会变得病毒式传播并传播到您的 Main 方法(您将在其中“等待”)?
  • 斯蒂芬,我还有一个问题。我尝试将任务分配给您的示例中的变量。事实上,它使编译器静音,但任务仍然没有等待。在这种情况下编译器不应该发出警告吗?
  • @ZunTzu:1)是的;这就是大多数 (>98%) 异步程序的工作方式。 2) 编译器尽最大努力发出警告,但它并不(不可能)完美。
  • 好的,谢谢。顺便说一句,我刚刚发现了TplExtensions.Forget。该规范说“对于在异步方法中对异步方法的即发即弃调用很有用”。它们实际上是指“...在同步方法中”吗?
  • @ZunTzu 不,他们不是那个意思。从同步方法调用异步方法通常是错误的根本。解决方案通常不是一劳永逸,而是一开始就不这样做。至于您的第一条评论中的问题,在大多数异步程序中,您将在程序中处理已发布消息的所有异步的顶层有一个消息循环(或一些类似的概念),然后可以继续触发额外的异步操作。
【解决方案3】:

一个潜在的问题点是,现在无法判断代码是否引发了异常。所以如果你有单元测试来检测这些,单元测试将永远无法工作。

来自 MSDN 网站的经典示例:

私有异步无效 ThrowExceptionAsync() { 抛出新的 InvalidOperationException(); } 公共无效 AsyncVoidExceptions_CannotBeCaughtByCatch() { 尝试 { ThrowExceptionAsync(); } 捕捉(例外) { // 这里永远不会捕获异常! 扔; } }

http://haacked.com/archive/2014/11/11/async-void-methods/

https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

不使用async void,如果这是一个特定的边缘情况,使用pragma statements 怎么样?

#pragma warning disable CS-4014
... your code here ...
#pragma warning restore CS-4014

这样您就可以消除静电噪音。

HTH...

【讨论】:

  • 我知道这个限制。这就是为什么我明确写了 FireAndForget 不能抛出异常。
  • 是的,async void 的具体预期用途实际上是用于事件处理程序,因此应避免任何外部使用,恕我直言。请改用编译指示。
【解决方案4】:

有时您想一劳永逸,但您应该始终希望让阅读您的代码的人明白这一点。我发现“_”符号不够明确,所以我是这样做的,使用扩展方法:

public static class TaskExtensions()
{
    public static void InvokeAndIgnore(this Task fireAndForgetTask)
    {
        // deliberately do nothing; used to suppress warning
    }
}

您可以按如下方式使用它:

worker.AttemptCloseAsync().InvokeAndIgnore();

【讨论】:

  • 好主意!谢谢。
  • 出于什么原因,您将Async 后缀附加到IgnoreAsync 方法的名称?我不认为这是一个异步方法,因为它不会返回 Task
  • 好点@TheodorZoulias,最好使用不以“Async”结尾的其他名称。
猜你喜欢
  • 2014-09-04
  • 2015-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-08
  • 1970-01-01
  • 2023-03-19
  • 2012-07-03
相关资源
最近更新 更多