C#中await/async异步编程采坑—async方法可能会同步执行
前言
在C# 5.0和.NET 4.5中,引入了基于await/async的异步编程模式,也称为“基于任务的异步编程模型 (TAP) ”。它有效地避免了异步任务回调嵌套的地狱,而且非常易于使用,但是深度理解它却比学会使用它要困难得多。
异步方法被同步执行的几种场景
await/async的异步方法一般会被分配到线程池中运行,也可以设置为启动新的线程执行,它一般不会阻塞当前调用的线程,例如:
async void DelayAsync()
{
await Task.Delay(100);
}
void Delay()
{
Thread.Sleep(100);
}
我们把Delay方法看作某一个耗时的方法,而DelayAsync可以算是它的异步版本,那么,在一个窗体程序中分别调用这两个方法:
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i 50; i++)
{
DelayAsync();
}
}
private void button2_Click(object sender, EventArgs e)
{
for (int i = 0; i 50; i++)
{
Delay();
}
}
从以上代码的执行会发现,点击button2时UI线程阻塞了5秒钟,而点击button1时则完全没有异样。
那么问题来了:async方法一定就会异步执行而不会阻塞线程吗?
答案是否定的!!!
为了解释async方法如何阻塞线程,下面我们构造一些场景来看这个问题。
同步执行场景一
考虑下面的FakeDelayAsync方法
async void FakeDelayAsync()
{
Delay();
}
以上代码中,使用async关键字标记上了FakeDelayAsync方法,但内部实现却是一个同步的Delay方法。
调用这个方法时,会有两种可能:
- ×系统将Delay方法插入线程池执行,当前线程不会被阻塞,因为这个方法是async方法。
- √系统直接调用Delay,当前线程阻塞。
实际执行一下,会发现执行FakeDelayAsync和直接执行Delay的表现是一样的,两者都是同步执行。实际上,在VisualStudio中打出这个方法时,就会有警告:
此异步方法缺少 “await” 运算符,将以同步方式运行。请考虑使用 “await” 运算符等待非阻止的 API 调用,或者使用 “await Task.Run(…)” 在后台线程上执行占用大量 CPU 的工作。
同步执行场景二
考虑下面的FakeDelayAsync2和FakeDelayAsync3方法
async Task FakeDelayAsync2()
{
Delay();
}
async void FakeDelayAsync3()
{
await FakeDelayAsync2();
}
在FakeDelayAsync3方法中,VisualStudio没有任何警告,那么在调用这个方法时,会有两种可能:
- ×系统将FakeDelayAsync2方法插入线程池执行,当前线程不会被阻塞,因为这个方法是async Task。
- √系统直接调用Delay,当前线程阻塞。
实际执行以上代码,就会发现FakeDelayAsync3和Delay的表现还是一样的。即便VisualStudio在FakeDelayAsync3中没有给出任何的警告,但它调用FakeDelayAsync2方法仍然是同步执行的!!!
同步执行场景三
也许你会认为,FakeDelayAsync3中虽然没有警告,但FakeDelayAsync2中也有啊!那么,考虑下面的情形:
async Task HalfFakeDelayAsync(bool isAsync)
{
if (isAsync)
{
Delay();
}
else
{
await Task.Delay(100);
}
}
async void FakeDelayAsync4()
{
await HalfFakeDelayAsync(true);
}
构建一个存在await关键字的HalfFakeDelayAsync方法,再在FakeDelayAsync4中调用它。
那么,调用FakeDelayAsync4方法时,即便VisualStudio完全没有给出任何警告,但是它依然是同步执行。
同步执行场景四
你也许会认为,上面的几个场景都是别有用心的构造出来的,或是因为“错误的”编程而导致async方法被同步执行的,那么,我们可以考察下面这个完全使用.NET Framework所提供async方法的例子:
async void FakeDelayAsync5()
{
SemaphoreSlim semaphore = new SemaphoreSlim(int.MaxValue);
for (int i = 0; i 10000000; i++)
{
await semaphore.WaitAsync();
}
}
这个FakeDelayAsync5方法,仍然是同步执行的!!!
总结
总结以上四个场景,实际上,我们可以认为:
- 不论是async void还是async Task,不论VisualStudio有没有给出警告,都有可能以同步的方式执行,从而阻塞调用线程。
- 一个async void或者async Task究竟是否会以异步方式执行,取决于其本身内部逻辑是否会释放当前线程。
- 因此,被标记为async的方法,仅仅是异步的一个必要不充分条件。
相关文章
- 【说站】css3中设置元素宽度的方法
- 【说站】java方法重载的无效探究
- 【说站】java重载方法的参数设置
- 【说站】javascript判断变量相等的方法整理
- 【说站】python生成器创建的方法整理
- 【说站】css设置文字居中的两种方法
- 【说站】java方法重载
- 【说站】PHP中define定义常量的方法
- 【说站】python try-except捕获异常的方法
- 【说站】python对象方法是什么
- sourcetree安装跳过注册方法
- 【说站】python dict实现的魔法方法
- 【说站】java多线程有几种实现方法
- 【说站】python int返回的方法探究
- 【说站】java反射获取对象的方法
- 【说站】java8中的四种方法引用
- 【说站】python关闭文件的两种方法
- 【说站】python赋值和交换的方法
- 【说站】java接口中静态方法的继承
- 【说站】java虚拟扩展方法如何实现