为什么一个“;async void”;方法同步运行

本文关键字:void async 方法 运行 同步 一个 为什么 | 更新日期: 2023-09-27 18:22:00

Eric Lippert说:

不能等待返回void的异步方法;这是一场"火灾和forget"方法。它确实异步工作

它确实异步工作,对吗

为了测试这一点,我制作了一个windows窗体应用程序,并处理了一个任意事件。在处理程序内部,我开始了繁重的计算。很明显,它阻止了UI的响应:

this.KeyPress += Form1_KeyPressed;
....
private async void Form1_KeyPressed(object sender, EventArgs e)
{
   for(int i=0; i<int.max; i++)
      ;
}

孙的回答中我遗漏了什么?

为什么一个“;async void”;方法同步运行

孙的回答中我遗漏了什么?

我的意思是,它的工作原理与任何其他异步方法一样。当您在异步方法中等待某个东西时,该方法的剩余部分将被注册为等待的东西的延续。无论异步方法是否为void,都是如此。

在您的示例中,您的代码的工作方式与返回任务的异步方法完全相同。尝试更改方法以返回任务,您会看到它的行为完全相同。

请记住,"async"并不意味着"我在另一个线程上并发运行"。这意味着"该方法可能在其操作完成之前返回"。它在行动完成之前可能返回的点标有"等待"。您没有用"等待"标记任何内容。

我怀疑你相信异步需要并发的神话。同样:异步只是意味着一个方法可以在其工作完成之前返回。你开始煮鸡蛋,门铃响了,你把包裹从门廊上拿下来,你煮完鸡蛋,打开包裹。"煮鸡蛋"answers"取邮件"工作不是同时进行的——你从来没有同时进行过。它们是异步

async关键字所做的只是允许您等待方法中的异步操作(并将结果封装在任务中)。

每个异步方法都同步运行,直到到达第一个等待。如果您不等待任何事情,那么这个方法(无论它是否返回任务)都将同步运行。

如果你的方法是同步的,你通常根本不需要使用异步等待。但是,如果你想将CPU密集型操作卸载到另一个线程,这样UI线程就不会被长时间阻塞,你可以使用Task.Run:

await Task.Run(() => CPUIntensiveMethod());

只有使用async分配方法不会使其异步调用。对于异步调用,它需要一个在正文中包含await关键字的方法调用。

关于这个:

不能等待返回void的异步方法;这是一场"火灾和忘记"方法。它确实异步工作…

这意味着您不能在类似这样的另一个方法中等待This方法。

await Form1_KeyPressed(this, EventArgs.Empty)

为了让你的代码工作,你需要一个带有await关键字的方法,比如:

private async void Form1_KeyPressed(object sender, EventArgs e)
{
   for(int i=0; i<int.max; i++)
      ;
   // In the body some code like this
   await YourMethod();
}

更新版本

"异步"关键字

"async"关键字在应用于方法时会做什么?

当你用"async"关键字标记一个方法时,你实际上告诉编译器两件事:

  1. 您告诉编译器,您希望能够在方法中使用"await"关键字(当且仅当方法或lambda中的方法或lambda被标记为async时,您才能使用await关键字)。在这样做的过程中,您告诉编译器使用状态机编译该方法,这样该方法将能够挂起,然后在等待点异步恢复
  2. 您告诉编译器"提升"方法的结果或返回类型中可能出现的任何异常。对于返回Task或Task的方法,这意味着在该方法中未处理的任何返回值或异常都将存储到结果任务中。对于返回void的方法,这意味着任何异常都会通过方法初始调用时的当前"SynchronizationContext"传播到调用方的上下文

在方法上使用"async"关键字是否会强制该方法的所有调用都是异步的

没有。当您调用一个标记为"async"的方法时,它开始在curren线程上同步运行因此,如果您有一个返回void的同步方法,而您所要做的只是将其标记为"async",则该方法的调用仍将同步运行无论您是将返回类型保留为"void"还是将其更改为"Task",这都是正确的。类似地,如果您有一个返回一些TResult的同步方法,并且您所做的只是将其标记为"async"并将返回类型更改为"Task",则该方法的调用仍将同步运行。

将方法标记为"async"不会影响该方法是同步运行还是异步运行。相反,它允许将方法拆分为多个部分,其中一些部分可以异步运行,从而使方法可以异步完成。这些片段的边界只能出现在使用"await"关键字显式编码的地方,因此,如果在方法的代码中根本不使用"wait",则只有一个片段,并且由于该片段将开始同步运行,因此它(以及使用它的整个方法)将同步完成。

欲了解更多信息,请点击此处:

http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/async-await-faq.aspx

Async/await允许您创建可以异步运行的代码。它甚至可以并行运行(与异步正交)。但是,它是否并行运行取决于任务的计划方式

当从UI或ASP.NET上下文调用代码时(更具体地说,从具有这些框架的UI控件的上下文中的主线程调用代码,因为大多数控件只能在拥有它们的线程上访问),默认情况下不会将代码调度到后台线程。您仍然会看到,代码的执行将等待Task完成后再继续,但由于Task被调度到同一个(主)线程,它将阻止该线程上的任何其他操作(例如,处理UI事件)。

如果你知道你的代码可以在后台线程中安全执行(同样,这通常是因为你知道你不是访问任何线程仿射的控制属性),你可以用ConfigureAwait:覆盖调度行为

await DoWorkAsync().ConfigureAwait(false);

另请参阅:Async/Await-异步编程中的最佳实践

Async只是您可能使用异步方法的声明。如果你要创建一个类似的任务

private async Task<int> callMe()
{
   int i;
   for(i = 0; i<int.max; i++)
      ;
   return i;
} 

您可以使用waitcallMe()行运行代码在您的KeyPressed事件中。当然,你不必返回任何值,但这只是一个实际的例子。

仅仅因为声明了一个方法async,它不会自动在另一个线程中运行
你必须自己用Task.Run()开始一个。

所以在你的情况下,这将是:

private async void Fomr1_KeyPressed(object sender, EventArgs e)
{
   await Task.Run(() => {
       for(int i=0; i<int.max; i++);
   });
}

async/await的一些良好起点:
使用Async和Await进行异步编程
用.NET进行并行编程
任务调度器

我发现Filip Ekberg的解释非常有启发性:

现在重要的是要记住,await之后的一切关键字以连续方式执行。使用之间的区别CCD_ 10和CCD_调用方线程,在本例中为UI线程。

因此:

private async void Form1_KeyPressed(object sender, EventArgs e)
{
   // ----------------------> Surely, on UI thread
   await Something(); ------> (May be) on new thread
   // ----------------------> Surely, on UI thread
   for(int i=0; i<int.max; i++)
      ;
}