在什么情况下会使用
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));
}
通常,您会希望返回一个Task。主要的例外应该是当你需要一个void返回类型(用于事件)时。如果没有理由不允许调用者等待任务,为什么不允许呢?
返回void的异步方法在另一个方面是特殊的:它们表示顶级异步操作,并且在任务返回异常时具有额外的规则。最简单的方法就是用一个例子来说明区别:
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));
}
是的,在这里使用async和await,它们确保你的方法在抛出异常时仍然正确工作。
欲了解更多信息,请参阅:https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
通常,您会希望返回一个Task。主要的例外应该是当你需要一个void返回类型(用于事件)时。如果没有理由不允许调用者等待任务,为什么不允许呢?
返回void的异步方法在另一个方面是特殊的:它们表示顶级异步操作,并且在任务返回异常时具有额外的规则。最简单的方法就是用一个例子来说明区别:
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));
}
是的,在这里使用async和await,它们确保你的方法在抛出异常时仍然正确工作。
欲了解更多信息,请参阅:https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
我偶然发现了Jérôme Laban写的关于async和void的非常有用的文章:
https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html
底线是async+void会使系统崩溃,通常只应该在UI侧事件处理程序上使用。
The reason behind this is the Synchronization Context used by the
AsyncVoidMethodBuilder, being none in this example. When there is no
ambient Synchronization Context, any exception that is unhandled by
the body of an async void method is rethrown on the ThreadPool. While
there is seemingly no other logical place where that kind of unhandled
exception could be thrown, the unfortunate effect is that the process
is being terminated, because unhandled exceptions on the ThreadPool
effectively terminate the process since .NET 2.0. You may intercept
all unhandled exception using the AppDomain.UnhandledException event,
but there is no way to recover the process from this event.
When writing UI event handlers, async void methods are somehow
painless because exceptions are treated the same way found in
non-async methods; they are thrown on the Dispatcher. There is a
possibility to recover from such exceptions, with is more than correct
for most cases. Outside of UI event handlers however, async void
methods are somehow dangerous to use and may not that easy to find.
调用async void的问题是
你连任务都拿不回来。您无法知道函数的任务何时完成。——速成班在async和等待|旧的新事物
下面是调用async函数的三种方法:
async Task<T> SomethingAsync() { ... return t; }
async Task SomethingAsync() { ... }
async void SomethingAsync() { ... }
In all the cases, the function is transformed into a chain of tasks. The difference is what the function returns.
In the first case, the function returns a task that eventually produces the t.
In the second case, the function returns a task which has no product, but you can
still await on it to know when it has run to completion.
The third case is the nasty one. The third case is like the second case, except
that you don't even get the task back. You have no way of knowing when
the function's task has completed.
The async void case is a "fire and
forget": You start the task chain, but you don't care about when it's
finished. When the function returns, all you know is that everything
up to the first await has executed. Everything after the first await
will run at some unspecified point in the future that you have no
access to.