繁体   English   中英

为什么“异步无效”方法会同步运行?

[英]Why does an “async void” method run synchronously?

埃里克·利珀特(Eric Lippert) 在这里说:

无法等待void返回的异步方法; 这是一种“忘了忘了”的方法。 它确实异步工作 ...

它确实异步工作对吗?

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

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

我在埃里克的答案中遗漏了什么?

我在埃里克的答案中遗漏了什么?

我的意思是,它的工作方式与任何其他异步方法一样。 当您在异步方法中等待某些内容时,该方法的其余部分将被标记为等待的内容的继续 无论异步方法是否无效,都是如此。

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

记住,“异步”并不意味着“我在另一个线程上同时运行”。 这意味着“该方法可能在其操作完成之前返回”。 在操作完成之前可能返回的点标记为“等待”。 您尚未将任何内容标记为“等待”。

我怀疑您相信异步需要并发的神话。 再次: 异步只是意味着方法可以在其工作完成之前返回 您开始煮一些鸡蛋,门铃响起,然后将包装从门廊上取下来,煮完鸡蛋,然后打开包装。 “煮鸡蛋”和“获取邮件”作业不是同时进行的 ,您永远不会同时进行 它们是异步的

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

每个异步方法都将同步运行,直到到达第一个等待状态。 如果您不等待任何东西,则此方法(无论它是否返回任务)将同步运行。

如果您的方法是同步的,则通常根本不需要使用异步等待。 但是,如果您希望将CPU密集型操作卸载到其他线程上,以便长时间不阻止UI线程,则可以使用Task.Run

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

仅分配带有async方法不会使其异步调用。对于异步调用,需要在其主体中使用await关键字进行方法调用。

关于此:

无法等待void返回的异步方法; 这是一种“忘了忘了”的方法。 它确实异步工作...

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

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标记为异步时才可以使用await关键字)。 这样做是在告诉编译器使用状态机编译该方法,这样该方法将能够挂起,然后在等待点异步恢复。
  2. 您要告诉编译器“提升”方法的结果或返回类型中可能发生的任何异常。 对于返回Task或Task的方法,这意味着该方法中未处理的任何返回值或异常都存储在结果任务中。 对于返回void的方法,这意味着任何异常都会通过该方法初始调用时当前使用的任何“ SynchronizationContext”传播到调用者的上下文。

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

否。当您调用标记为“异步”的方法时,该方法开始在curren线程上同步运行。 因此,如果您有一个返回void的同步方法,并且您所做的所有更改都将其标记为“异步”,则该方法的调用仍将同步运行。 无论您将返回类型保留为“ void”还是将其更改为“ Task”,都是如此。 同样,如果您有一个同步方法返回一些TResult,而您所做的只是将其标记为“异步”并将返回类型更改为“任务”,则该方法的调用仍将同步运行。

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

有关更多信息,请参见此处:

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

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

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

如果知道您的代码可以安全地在后台线程中执行(再次,这通常是因为您知道您没有访问任何线程仿射的控件属性),则可以使用ConfigureAwait覆盖调度行为:

await DoWorkAsync().ConfigureAwait(false);

另请参见: 异步/等待-异步编程最佳实践

异步只是您可以使用异步方法的声明。 如果您要创建一个任务,例如

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

您可以使用await callMe()行来运行代码 在您的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关键字之后的所有内容都将连续执行。 使用awaitContinueWith之间的区别在于,实际上延续是在调用者线程上进行的,在这种情况下,调用者线程是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++)
      ;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM