[英]Does await Task.CompletedTask mean the async method will run synchronously?
[英]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”关键字标记方法时,实际上是在告诉编译器两件事:
在方法上使用“ 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
关键字之后的所有内容都将连续执行。 使用await
和ContinueWith
之间的区别在于,实际上延续是在调用者线程上进行的,在这种情况下,调用者线程是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.