异步等待性能?

(只是一个理论问题 – 对于非gui应用程序)

假设我有很多awaits代码:

 public async Task ConsumeAsync() { await A(); await b(); await c(); await d(); //.. } 

每项任务可能需要很短的时间,

问题 (再次,理论上的)

可能存在这样一种情况: 整个时间处理所有那些“释放回线程”和“取回线程”(红色和绿色在这里:)

在此处输入图像描述

花费更多的时间比单个线程可以完成所有工作并且有少量延迟,

我的意思是,我想成为最富有成效的,但是,因为所有这些来回切换 – 我实际上失去了生产力。

这种情况会发生吗?

Task对象表示挂起操作的延迟结果。 如果您没有任何挂起操作,则不必使用任务和async/await 。 否则,我相信async / await代码通常比它的裸TPL ContinueWith模拟更有效。

我们来做一些时间:

 using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication { class Program { // async/await version static async Task Test1Async(Task task) { return await task; } // TPL version static Task Test2Async(Task task) { return task.ContinueWith( t => t.Result, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } static void Tester(string name, Func, Task> func) { var sw = new System.Diagnostics.Stopwatch(); sw.Start(); for (int i = 0; i < 10000000; i++) { func(Task.FromResult(0)).Wait(); } sw.Stop(); Console.WriteLine("{0}: {1}ms", name, sw.ElapsedMilliseconds); } static void Main(string[] args) { Tester("Test1Async", Test1Async); Tester("Test2Async", Test2Async); } } } 

输出:

 Test1Async:1582ms
 Test2Async:4975ms

因此,默认情况下, await continuation的处理比ContinueWith continuation更有效。 让我们稍微优化一下这段代码:

 // async/await version static async Task Test1Async(Task task) { if (task.IsCompleted) return task.Result; return await task; } // TPL version static Task Test2Async(Task task) { if (task.IsCompleted) return Task.FromResult(task.Result); return task.ContinueWith( t => t.Result, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } 

输出:

 Test1Async:1557ms
 Test2Async:429ms

现在,非异步版本获胜。 对于async版本,我相信这种优化已经由async/await基础设施在内部完成。

无论如何,到目前为止,我们只处理完成的任务( Task.FromResult )。 让我们介绍一下实际的异步(当然,这次我们会做更少的迭代):

 static Task DoAsync() { var tcs = new TaskCompletionSource(); ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(0)); return tcs.Task; } static void Tester(string name, Func, Task> func) { ThreadPool.SetMinThreads(200, 200); var sw = new System.Diagnostics.Stopwatch(); sw.Start(); for (int i = 0; i < 1000000; i++) { func(DoAsync()).Wait(); } sw.Stop(); Console.WriteLine("{0}: {1}ms", name, sw.ElapsedMilliseconds); } 

输出:

 Test1Async:4207ms
 Test2Async:4734ms

现在差异非常小,尽管async版本仍然表现稍好一些。 然而,我认为这种增益实际上是可以忽略的,与异步操作的实际成本相当,或者与SynchronizationContext.Current != null时恢复捕获的上下文的成本相当。

最重要的是,如果您处理异步任务,请选择async / await如果您有选择,不是出于性能原因,而是为了易用性,可读性和可维护性。

是的,从理论上讲。 通常情况下,在现实世界中。

在常见的情况下, async用于I / O绑定操作,并且与它们相比,线程管理的开销是不可检测的。 大多数情况下,异步操作要么花费很长时间(与线程管理相比),要么已经完成(例如,缓存)。 请注意, async有一个“快速路径”,如果操作已经完成,它就会启动,它不会产生线程。

有关更多信息,请参阅Async和Async Performance的Zen 。

是的,它可能发生。 另外不要忘记 – 你可以编程的所有效率 – 任务系统都有开销。

如果你对这样的事情太过粗暴,那么同步开销可以杀死你。 这意味着:任务是非常有效的编程。

但旧规则坚持:不要超细颗粒。 SOmetimes优化有帮助。

这种情况会发生吗?

绝对。 因此,您应该认真对待使用异步代码的位置。 通常,您最好将它用于实际执行异步操作的方法(例如,磁盘或网络I / O)。 这些操作所花费的时间通常远远超过在线程上调度任务的成本。 此外,在操作系统级别,这些类型的操作本质上是异步的,因此您实际上是通过使用异步方法删除了一个抽象层。

但是,即使在这些情况下,除非您能够利用并发性,否则通过切换到异步代码可能看不到明显的性能差异。 例如,您发布的代码可能看不到实际的性能增益,除非它被更改为这样的内容:

 await Task.WhenAll(new[]{A(), B(), C(), D(), ...}); 

是的,当然可以发生。 利用创建状态机的所有开销,来回控制并使用IOCP线程。 但正如所说, TPL非常优化。 例如,不要忘记,如果您的TaskAwaitable快速完成,则可能没有开销,并且它将同步执行,这可能通常在快速操作时发生。