添加' async'是否有任何缺点?功能关键字?

时间:2014-08-21 05:38:24

标签: c# asynchronous async-await

当我尝试在.net项目中使用async / await时,终于发现几乎所有函数都有async关键字,因为当它们等待其他异步函数时,它们本身应该是异步的。

例如,当我在某些异步IO lib中使用函数时:

async Task Foo(){
    await file.ReadAsyc();
}

然后调用Foo()的函数也应该是异步的。

async Task Bar() {
    await Foo();
}

最终,我发现很多函数都有async关键字。

是否存在任何不利因素(性能受损或其他方面)?

4 个答案:

答案 0 :(得分:3)

当方法标有async时,将在代码中生成状态机。此状态机包含大量代码。当您编写“真正的”并发代码时,这应该不是问题,因为使用async和await的好处应该超过额外代码的成本。但是,使用异步标记库中的每个函数都是没有意义的,并且会导致代码膨胀。此外,如果异步方法没有等待,编译器将生成警告。

所以,是的,使用异步有一个“缺点”,因为为状态机生成了额外的代码。但是,如果您正在编写并发代码,这不应成为避免使用异步和等待的理由。

为了比较,这里是一个空函数的IL。

没有异步:

void F() { }
F:
IL_0000:  ret 

使用async:

async void F() { }
F:
IL_0000:  ldloca.s    00 
IL_0002:  ldarg.0     
IL_0003:  stfld       UserQuery+d__0.4__this
IL_0008:  ldloca.s    00 
IL_000A:  call        System.Runtime.CompilerServices.AsyncVoidMethodBuilder.Create
IL_000F:  stfld       UserQuery+d__0.t__builder
IL_0014:  ldloca.s    00 
IL_0016:  ldc.i4.m1   
IL_0017:  stfld       UserQuery+d__0.1__state
IL_001C:  ldloca.s    00 
IL_001E:  ldfld       UserQuery+d__0.t__builder
IL_0023:  stloc.1     
IL_0024:  ldloca.s    01 
IL_0026:  ldloca.s    00 
IL_0028:  call        System.Runtime.CompilerServices.AsyncVoidMethodBuilder.Start
IL_002D:  ret         

d__0.MoveNext:
IL_0000:  ldc.i4.1    
IL_0001:  stloc.0     
IL_0002:  leave.s     IL_001B
IL_0004:  stloc.1     
IL_0005:  ldarg.0     
IL_0006:  ldc.i4.s    FE 
IL_0008:  stfld       UserQuery+d__0.1__state
IL_000D:  ldarg.0     
IL_000E:  ldflda      UserQuery+d__0.t__builder
IL_0013:  ldloc.1     
IL_0014:  call        System.Runtime.CompilerServices.AsyncVoidMethodBuilder.SetException
IL_0019:  leave.s     IL_002E
IL_001B:  ldarg.0     
IL_001C:  ldc.i4.s    FE 
IL_001E:  stfld       UserQuery+d__0.1__state
IL_0023:  ldarg.0     
IL_0024:  ldflda      UserQuery+d__0.t__builder
IL_0029:  call        System.Runtime.CompilerServices.AsyncVoidMethodBuilder.SetResult
IL_002E:  ret         

d__0.SetStateMachine:
IL_0000:  ldarg.0     
IL_0001:  ldflda      UserQuery+d__0.t__builder
IL_0006:  ldarg.1     
IL_0007:  call        System.Runtime.CompilerServices.AsyncVoidMethodBuilder.SetStateMachine
IL_000C:  ret         

状态机还消耗少量存储空间来跟踪状态,但这无关紧要。

答案 1 :(得分:2)

  

是否存在任何不利因素(性能受损或其他方面)?

通常,如果您使用真正的异步操作(通常是I / O绑定),则异步的任何性能下降都会由更高的可伸缩性和/或响应更快的UI组成。最重要的关键是:只有在通过分析表明性能是您必须解决的问题时才能优化性能。

正如其他人所指出的,async确实会导致生成状态机,占用内存并具有更大的代码大小。对于性能影响的最佳资源是Stephen Toub的MSDN articleChannel9 video

请注意,有一个"便宜"您可以采取的优化:

async Task FooAsync() {
  await file.ReadAsync();
}

与:

相同
Task FooAsync() {
  return file.ReadAsync();
}

当您执行方法重载等操作并且不想多次添加async开销时,这是一个很好的技巧。但是,如果您在方法中有其他代码,那么您可能只想使用asyncawait

答案 2 :(得分:1)

让我们看一下async修饰符的MSDN定义:

  

使用async修饰符指定方法,lambda表达式或匿名方法是异步的。如果在方法或表达式上使用此修饰符,则将其称为异步方法。

他们继续说道:

  

该方法同步运行,直到它到达第一个 await 表达式,此时方法将暂停,直到等待的任务完成。在此期间,控制将返回到方法的调用者,如下一节中的示例所示。

     

如果async关键字修改的方法不包含await表达式或语句,则该方法将同步执行。编译器警告会提醒您任何不包含await的异步方法,因为这种情况可能表示错误。

async关键字与await结合使用时会发挥作用。这是一个告诉编译器的标志“这个方法应该编译为状态机,因此控制可以回退给调用者并在异步操作完成时继续并继续执行其余的代码”。

如果方法中不存在await关键字,它将从头到尾同步运行,但将发出状态代码,这是一个重要因素。

现在,当等待async方法时,编译器会生成一个状态机。这确实有成本,尽管框架团队确保它对您的代码性能影响最小。更重要的是,幕后有SynchronizationContext当你await(除非明确说明不是),它负责将继续编组回到原始线程。

有关内部的更多内容,尤其是在性能方面,请参阅异步性能:Understanding the Costs of Async and Await

答案 3 :(得分:1)

缺点是代码消耗这些基于异步模式的方法必须意识到它们是异步工作的。也就是说,调用异步方法的每个方法也必须转换为异步方法。

虽然async / await模式简化了异步编程,因为代码看起来像是同步的,但开发人员需要有关多线程的高级知识,以确保他们的代码不会陷入死锁,同步问题和意外行为。

例如,IIS和ASP.NET托管的代码需要特殊的专业化(ASP.NET异步页面,异步任务...),因为IIS进程模型的行为与Windows上的常规可执行应用程序不同。

最后,如果我们谈论一个简单的客户端应用程序(您的应用程序可能有无用的线程同步开销),多线程环境中的线程同步可能会有性能损失,但是一旦客户端应用程序需要很多I / O和并行化,异步编程将更好地工作,因为您将利用您的多核CPU。如果我们谈论GUI应用程序,异步编程将是在执行长时间运行的进程时避免UI阻塞的好方法。