[英]async/await - when to return a Task vs void?
在什么场景下想要使用
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
返回类型(对于事件)。 如果没有理由禁止调用者await
您的任务,为什么要禁止它?
返回void
async
方法在另一方面是特殊的:它们代表顶级异步操作,并且在您的任务返回异常时具有额外的规则。 最简单的方法是用一个例子来说明差异:
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));
}
是的, await
这里使用async
和await
,它们确保您的方法在抛出异常时仍然正常工作。
有关更多信息,请参阅: https : //docs.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 侧事件处理程序上使用。
这背后的原因是 AsyncVoidMethodBuilder 使用的同步上下文,在这个例子中没有。 当没有环境同步上下文时,异步 void 方法主体未处理的任何异常都会在 ThreadPool 上重新抛出。 虽然似乎没有其他合乎逻辑的地方可以抛出这种未处理的异常,但不幸的结果是进程被终止,因为自 .NET 2.0 以来,线程池上的未处理异常有效地终止了进程。 您可以使用 AppDomain.UnhandledException 事件拦截所有未处理的异常,但无法从该事件中恢复进程。
在编写 UI 事件处理程序时,异步 void 方法在某种程度上是无痛的,因为异常的处理方式与非异步方法中的处理方式相同; 它们被扔到调度器上。 有可能从此类异常中恢复,在大多数情况下是正确的。 然而,在 UI 事件处理程序之外,异步 void 方法使用起来有些危险,并且可能不容易找到。
调用 async void 的问题在于
你甚至没有拿回任务。 您无法知道函数的任务何时完成。 ——异步和等待的速成课程| 旧的新事物
以下是调用异步函数的三种方式:
async Task<T> SomethingAsync() { ... return t; } async Task SomethingAsync() { ... } async void SomethingAsync() { ... }
在所有情况下,该功能都被转换为一系列任务。 不同之处在于函数返回的内容。
在第一种情况下,该函数返回一个最终产生 t 的任务。
在第二种情况下,该函数返回一个没有产品的任务,但您仍然可以等待它以了解它何时运行完成。
第三种情况是令人讨厌的。 第三种情况与第二种情况类似,只是您甚至没有取回任务。 您无法知道函数的任务何时完成。
异步无效的情况是“即发即忘”:您启动任务链,但您不关心它何时完成。 当函数返回时,您所知道的就是直到第一个 await 为止的所有内容都已执行。 第一次 await 之后的所有内容都将在未来某个您无法访问的未指定点运行。
我从这句话中得到了清晰的主意。
异步无效方法的异常无法捕获
private async void ThrowExceptionAsync()
{
throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
try
{
ThrowExceptionAsync();
}
catch (Exception)
{
// The exception is never caught here!
throw;
}
}
可以使用AppDomain.UnhandledException或类似的GUI / ASP.NET应用程序的全部捕获事件来观察这些异常,但是使用这些事件进行常规异常处理是导致无法维护的秘诀(会使应用程序崩溃)。
异步void方法具有不同的构成语义。 返回任务或任务的异步方法可以使用await,Task.WhenAny,Task.WhenAll等轻松组成。 返回void的异步方法不能提供一种简单的方法来通知调用代码它们已完成。 启动多个异步void方法很容易,但是确定它们何时完成并不容易。 异步void方法在启动和完成时会通知其SynchronizationContext,但是自定义SynchronizationContext是常规应用程序代码的复杂解决方案。
使用同步事件处理程序时,Async Void方法很有用,因为它们直接在SynchronizationContext上引发异常,这与同步事件处理程序的行为类似
有关更多详细信息,请检查此链接https://msdn.microsoft.com/zh-cn/magazine/jj991977.aspx
我认为你也可以使用async void
来启动后台操作,只要你小心捕捉异常。 想法?
class Program {
static bool isFinished = false;
static void Main(string[] args) {
// Kick off the background operation and don't care about when it completes
BackgroundWork();
Console.WriteLine("Press enter when you're ready to stop the background operation.");
Console.ReadLine();
isFinished = true;
}
// Using async void to kickoff a background operation that nobody wants to be notified about when it completes.
static async void BackgroundWork() {
// It's important to catch exceptions so we don't crash the appliation.
try {
// This operation will end after ten interations or when the app closes. Whichever happens first.
for (var count = 1; count <= 10 && !isFinished; count++) {
await Task.Delay(1000);
Console.WriteLine($"{count} seconds of work elapsed.");
}
Console.WriteLine("Background operation came to an end.");
} catch (Exception x) {
Console.WriteLine("Caught exception:");
Console.WriteLine(x.ToString());
}
}
}
根据 Microsoft 文档,永远不要使用async void
不要这样做:以下示例使用
async void
使 HTTP 请求在到达第一个等待时完成:
这在 ASP.NET Core 应用程序中总是一个不好的做法。
在 HTTP 请求完成后访问 HttpResponse。
使进程崩溃。
我的回答很简单,你不能等待 void 方法
Error CS4008 Cannot await 'void' TestAsync e:\test\TestAsync\TestAsyncProgram.cs
因此,如果该方法是异步的,最好是可等待的,因为您可能会失去异步优势。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.