使用async-await可以为您带来任何性能优势吗?

时间:2016-04-17 23:44:23

标签: c# .net performance asynchronous async-await

每当我阅读async - await时,用例示例始终,其中有一个您不想冻结的UI。所有编程书籍/教程都是相同的,或者阻止UI是async - await作为开发人员应该知道的唯一情况。

是否有任何示例可以说明如何使用async - await来提高算法的性能优势?就像让我们采取任何经典的编程面试问题:

  • 在二叉树中查找最近的共同祖先
  • 给定a[0]a[1],...,a[n-1]表示基数为10的数字,找到使用相同数字的下一个最高数字
  • 找出两个排序数组的中位数(即,如果要将它们合并,则为中位数)
  • 给定一组数字12,...,n,如果缺少一个号码,请找到丢失的号码
  • 查找数组中最大的2个数字

有没有办法使用async - await来提升效果?如果是这样,如果你只有1个处理器怎么办?那么你的机器不是在两个任务之间划分时间而不是真正同时执行它们吗?

6 个答案:

答案 0 :(得分:38)

this interview, Eric Lippert compared async await with a cook making breakfast。它帮助我理解了async-await的好处。在中间某处搜索'async-await'

假设厨师必须做早餐。他必须烤面包,煮一些鸡蛋,也可以煮一些茶?

方法1:同步。由一个线程执行。你开始烘烤面包。等到面包烤好了。取出面包。开始加水,等到水沸腾,然后插入鸡蛋。等到鸡蛋准备好然后取出鸡蛋。开始为茶喝水。等到水煮沸后再煮茶。

你会看到所有的等待。当线程在等待它可以做其他事情。

方法2:异步 - 等待,仍然是一个线程你开始烘烤面包。当面包被烤时,你开始为鸡蛋和茶开水。然后你开始等待。完成三个任务中的任何一个后,您将完成任务的第二部分,具体取决于首先完成的任务。因此,如果鸡蛋的水首先煮沸,你煮鸡蛋,再等待任何任务完成。

在这个描述中,只有一个人(你)正在做所有的事情。只涉及一个线程。好处是,因为只有一个线程在执行这些操作,所以代码看起来与读者非常同步,并且不需要使变量线程安全。

很容易看出,这样你的早餐将在更短的时间内准备好(你的面包仍然会很温暖!)。在计算机生活中,当线程必须等待另一个进程完成时,这些事情就会发生,例如将文件写入磁盘,从数据库或从Internet获取信息。这些函数通常会看到函数的异步版本:WriteWriteAsyncReadReadAsync

  

补充:在其他用户的一些评论和一些测试后,我发现事实上它可以是任何线程在等待之后继续你的工作。这个其他线程具有相同的“上下文”,因此可以表现为它是原始线程。

方法3:雇用厨师烤面包,煮茶时煮鸡蛋:真正异步。几个线程这是最昂贵的选项,因为它涉及创建单独的线程。在制作早餐的例子中,这可能不会非常加快这个过程,因为相对较长的过程你无论如何都没有做任何事情。但是,例如,如果您还需要切片番茄,那么当您使用async-await执行其他操作时,让烹饪(单独的线程)执行此操作可能会很方便。当然,你做的其中一件等待厨师完成他的切片。

另一篇解释很多的文章是Async and Await由有用的Stephen Cleary写的。

答案 1 :(得分:17)

  

每当我读到async-await时,用例示例总是存在一个你不想冻结的UI。

这是async最常见的用例。另一个是服务器端应用程序,其中async可以提高Web服务器的可伸缩性。

  

是否有任何示例可以说明如何使用async-await来提高算法的性能优势?

没有

如果要进行并行处理,可以使用任务并行库。并行处理是使用多个线程,在系统中的多个核心之间划分算法的部分。并行处理是一种并发形式(同时做多个事情)。

异步代码完全不同。异步代码的要点是在操作进行时使用当前线程。异步代码通常是I / O绑定或基于事件(如计时器)。异步代码是另一种并发形式。

我的博客上有async introhow async doesn't use threads上有帖子。

请注意,任务并行库使用的任务可以安排到线程上并执行代码。基于任务的异步模式使用的任务没有代码,也没有“执行”。虽然两种类型的任务都由相同的类型(Task)表示,但它们的创建和使用方式完全不同;我在博客上更详细地描述了这些Delegate Tasks and Promise Tasks

答案 2 :(得分:5)

总之,非常一般 - 不,通常不会。但它需要更多的单词,因为"性能"可以通过多种方式理解。

Async / await'节省时间'只有在“工作”时才会是I / O绑定的。将它应用于受CPU限制的作业会引入一些性能命中。那是因为如果你的CPU有一些计算,即10秒,那么添加async / await - 即:任务创建,调度和同步 - 只需将X额外的时间添加到你仍然需要10秒的时间需要刻录你的CPU以完成工作。接近阿姆达尔定律的想法。不是真的,但非常接近。

然而,有一些'但是......

首先,由于引入async / await而导致的性能下降并不是那么大。 (特别是如果你小心不要过头的话)。

其次,由于async / await允许您更容易地编写I / O交错代码,因此您可能会注意到在您懒得的地方(:)删除I / O等待时间的新机会否则或在没有异步/等待语法良好的情况下使代码难以遵循的地方。例如,围绕网络请求拆分代码是相当明显的事情,但您可能会注意到,即您还可以在您编写CSV文件或读取配置文件等的少数几个位置升级某些文件i / o。这里的增益不会归功于async / await - 这要归功于重写处理文件i / o的代码。你可以在没有异步/等待的情况下做到这一点。

第三,由于某些i / o操作更容易,您可能会注意到将CPU密集型工作卸载到另一个服务或机器上要容易得多,这也可以提高您的感知性能(更短的'挂钟&# 39;时间),但整体资源消耗将增加:添加另一台机器,花费在网络操作上的时间等。

第四:UI。你真的不想冻结它。在Tasks和async / await中包装I / O绑定和CPU绑定作业非常容易,并保持UI响应。这就是为什么你看到它到处提到的原因。但是,虽然理想情况下I / O绑定操作应该是异步,但要在所有冗长的I / O上移除尽可能多的空闲等待时间,但CPU绑定的作业不需要分割或异步化而不仅仅是1水平下降。将巨大的单片计算工作包含在一个任务中就足以让UI解除阻塞。当然,如果你有许多处理器/核心,它仍然值得并行化内部可能的任何东西,但与I / O相反 - 分裂太多,你将忙于切换任务而不是咀嚼计算。

总结:如果你有时间进行I / O - 异步操作可以节省很多时间。很难过度异步I / O操作。如果您有CPU占用操作,那么添加任何东西将消耗更多的CPU时间和更多的内存,但由于将作业分成更小的部分,可以在同一个更多的内核上运行,因此挂钟时间可以更好时间。它不难过头,所以你需要小心一点。

答案 3 :(得分:2)

  

该方法在当前同步上下文上运行并使用时间   仅在方法处于活动状态时才在线程上。您可以使用Task.Run   将CPU绑定的工作移动到后台线程,但后台线程   对于等待结果变成的过程没有帮助   可用。

当您的应用程序中有一个CPU和多个线程时,CPU会在线程之间切换以模拟并行处理。使用async / await您的异步操作不需要线程时间,因此您可以为应用程序的其他线程提供更多时间来完成工作。例如,您的应用程序(非UI)仍然可以进行HTTP调用,您只需等待响应即可。这是使用async / await的好处很大的情况之一。

当您致电async DoJobAsync()时,请忘记.ConfigureAwait(false)以获得不需要合并回UI线程上下文的非UI应用的更好性能。

我没有提到很好的语法,这有助于保持代码清洁。

MSDN

答案 4 :(得分:2)

大多数情况下,你没有获得直接表现(你正在执行的任务发生得更快和/或内存更少),就像在可扩展性方面一样;使用较少的线程来执行相同数量的同时任务意味着您可以执行的同时任务数量更高。

因此,在大多数情况下,您并未发现某项操作在性能方面有所提高,但可以发现大量使用可提高性能。

如果某个操作需要涉及真正异步(多个异步I / O)的并行任务,那么该可伸缩性可以使单个操作受益。因为线程中发生的阻塞程度降低了,即使你只有一个核心,也会发生这种情况,因为机器只在那些当前没有等待的任务之间划分时间。

这与并行CPU绑定操作不同(无论是使用任务还是以其他方式完成)通常只会扩展到可用的核心数。 (在某些方面,超线程内核的行为类似于2个或更多内核,而在其他方面则不然。)

答案 5 :(得分:1)

  

async和await关键字不会导致创建其他线程。异步方法不需要多线程,因为异步方法不能在自己的线程上运行。该方法在当前同步上下文上运行,并仅在方法处于活动状态时在线程上使用时间。您可以使用Task.Run将受CPU限制的工作移动到后台线程,但后台线程对于只等待结果可用的进程没有帮助。

.NET的async-await功能与其他框架的功能没有什么不同。它没有提供性能优势,但它只允许在单个线程中的任务之间连续切换,而不是让一个任务阻塞线程。如果您想获得性能提升,请使用任务并行库。

访问https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx