async / await - 何时返回Task vs void?

问题描述 投票:406回答:6

在什么情况下会想要使用

public async Task AsyncMethod(int num)

代替

public async void AsyncMethod(int num)

我能想到的唯一情况是,您是否需要能够跟踪其进度的任务。

此外,在以下方法中,async和await关键字是否不必要?

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}
c# asynchronous .net-4.5
6个回答
360
投票

1)通常,你会想要返回一个Task。主要的例外是你需要有一个void返回类型(用于事件)。如果没有理由不允许调用者await你的任务,为什么不允许它?

2)返回asyncvoid方法在另一方面是特殊的:它们代表顶级异步操作,并且在您的任务返回异常时有其他规则可以发挥作用。最简单的方法是通过示例显示差异:

static async void f()
{
    await h();
}

static async Task g()
{
    await h();
}

static async Task h()
{
    throw new NotImplementedException();
}

private void button1_Click(object sender, EventArgs e)
{
    f();
}

private void button2_Click(object sender, EventArgs e)
{
    g();
}

private void button3_Click(object sender, EventArgs e)
{
    GC.Collect();
}

f的例外总是“被观察”。离开顶级异步方法的异常被简单地视为任何其他未处理的异常。从未观察到g的例外情况。当垃圾收集器来清理任务时,它会发现该任务导致异常,并且没有人处理该异常。当发生这种情况时,TaskScheduler.UnobservedTaskException处理程序运行。你永远不应该让这件事发生。要使用你的例子,

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

是的,在这里使用asyncawait,如果抛出异常,它们会确保您的方法仍能正常工作。

有关更多信息,请参阅:http://msdn.microsoft.com/en-us/magazine/jj991977.aspx


36
投票

我已经看过这篇关于由JérômeLaban撰写的关于qazxsw poi和async的非常有用的文章:qazxsw poi

最重要的是,void可能会使系统崩溃,并且通常只应在UI端事件处理程序上使用。

这背后的原因是AsyncVoidMethodBuilder使用的同步上下文,在此示例中为none。当没有环境同步上下文时,在ThreadPool上重新抛出异步void方法的主体未处理的任何异常。虽然似乎没有其他逻辑位置可以抛出那种未处理的异常,但不幸的结果是该进程被终止,因为ThreadPool上的未处理异常有效地终止了自.NET 2.0以来的进程。您可以使用AppDomain.UnhandledException事件拦截所有未处理的异常,但无法从此事件中恢复该进程。

在编写UI事件处理程序时,异步void方法在某种程度上是无痛的,因为异常的处理方式与非异步方法相同;他们被扔在Dispatcher上。有可能从这些例外中恢复,对大多数情况来说都是正确的。但是,在UI事件处理程序之外,异步void方法在某种程度上是危险的,并且可能不容易找到。


24
投票

我从这些陈述中得到了明确的想法。

  1. 异步void方法具有不同的错误处理语义。当异步任务或异步任务方法抛出异常时,将捕获该异常并将其放在Task对象上。使用async void方法,没有Task对象,因此异步void方法抛出的任何异常都将直接在SynchronizationContext上引发(SynchronizationContext表示可执行代码的位置“。)async void方法时处于活动状态开始

Async Void方法的例外情况无法通过Catch捕获

https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

对于GUI / ASP.NET应用程序,可以使用AppDomain.UnhandledException或类似的catch-all事件来观察这些异常,但是使用这些事件进行常规异常处理是不可维护性的一个因素(它会使应用程序崩溃)。

  1. 异步void方法具有不同的组合语义。返回任务或任务的异步方法可以使用await,Task.WhenAny,Task.WhenAll等轻松编写。返回void的异步方法不提供通知调用代码已完成的简单方法。启动几个异步void方法很容易,但要确定它们何时完成并不容易。 Async void方法将在启动和结束时通知其SynchronizationContext,但自定义SynchronizationContext是常规应用程序代码的复杂解决方案。
  2. Async Void方法在使用同步事件处理程序时很有用,因为它们直接在SynchronizationContext上引发异常,这类似于同步事件处理程序的行为方式

有关更多详细信息,请查看此链接async+void


10
投票

调用异步void的问题是你甚至没有得到任务,你无法知道函数的任务何时完成(参见private async void ThrowExceptionAsync() { throw new InvalidOperationException(); } public void AsyncVoidExceptions_CannotBeCaughtByCatch() { try { ThrowExceptionAsync(); } catch (Exception) { // The exception is never caught here! throw; } }

以下是调用异步函数的三种方法:

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

在所有情况下,该功能都转换为一系列任务。不同之处在于函数返回的内容。

在第一种情况下,函数返回最终产生t的任务。

在第二种情况下,该函数返回一个没有产品的任务,但您仍然可以等待它知道它何时运行完成。

第三种情况是令人讨厌的。第三种情况就像第二种情况,除了你甚至没有得到任务。您无法知道函数的任务何时完成。

async void case是一个“一劳永逸”:你启动任务链,但你不关心它什么时候结束。当函数返回时,你所知道的是第一个await的所有内容都已执行。第一次等待之后的所有内容将在未来的某个未指定的点上运行,您无权访问。


5
投票

我认为你也可以使用https://blogs.msdn.microsoft.com/oldnewthing/20170720-00/?p=96655开始后台操作,只要你小心捕捉异常。思考?

async Task<T> SomethingAsync() { ... return t; }
async Task SomethingAsync() { ... }
async void SomethingAsync() { ... }

0
投票

我的回答很简单,你可以等待void方法错误CS4008无法等待'void'TestAsync e:\ test \ TestAsync \ TestAsync \ Program.cs

因此,如果方法是异步的,那么最好等待,因为你可以放松异步优势。

© www.soinside.com 2019 - 2024. All rights reserved.