为何使用Task.WaitAll相比多次await无性能提升?
问题
我原本以为用Task.WaitAll替代多次await是更优选择,查了相关讨论后也觉得前者能提升性能,但实际测试却发现多次await的总耗时更短——Task.WaitAll耗时1346ms,多次await耗时995ms。以下是我的测试代码和Method1实现,想知道这是什么原因?
Task.WaitAll版本测试代码
Stopwatch sw = Stopwatch.StartNew(); var t1 = Method1() var t2 = Method2(); var t3 = Method3(); var t4 = Method4(); var t5 = Method5(); //note - in the real code there are like 10 await calls //which I changed to use like above code. Task.WaitAll(t1,t2,t3,t4,t5); for (int i = 0; i < 20; i++) { someObject[i].prop1 = t1.Result[i]; someObject[i].prop2 = t2.Result[i]; someObject[i].prop3 = t3.Result[i]; someObject[i].prop4 = t4.Result[i]; someObject[i].prop5 = t5.Result[i]; } sw.Stop(); Debug.WriteLine($"Time taken using wait all concept {sw.ElapsedMilliseconds}"); // Time taken using wait all concept 1346 // Time taken in **multiple** await is 995 ms
多次await版本测试代码
Stopwatch sw = Stopwatch.StartNew(); List<List<someObj1>> sth1 = await Method1() List<List<someObj2>> sth2 = await Method1() List<List<someObj3>> sth3 = await Method1() List<List<someObj4>> sth4 = await Method1() List<List<someObj5>> sth5 = await Method1() for (int i = 0; i < claimData.Count; i++) { someObject[i].prop1 = sth1[i]; someObject[i].prop2 = sth2[i]; someObject[i].prop3 = sth3[i]; someObject[i].prop4 = sth4[i]; someObject[i].prop5 = sth5[i]; } sw.Stop(); Debug.WriteLine($"Time taken in **multiple** await is {sw.ElapsedMilliseconds}"); // Time taken in **multiple** await is 995 ms
Method1实现
private async Task<List<someObject>> Method1() { List<List<someObject>> something = new List<List<someObject>>(); var storedProcCriteria = new { //set up the object with parameters. }; using (var db = _sqlConn.GetDbConnection()) { string sql = @"someStoredProcedure"; SqlMapper.GridReader results = await db.QueryMultipleAsync(sql, storedProcCriteria, commandType: CommandType.StoredProcedure); while (!results.IsConsumed) { data = results.Read<someObject>().ToList(); something.Add(data); } return something; } }
原因分析
- 数据库连接池限制:
Method1每次执行都会从连接池获取数据库连接。用Task.WaitAll时,5个任务同时发起请求,一旦超过连接池能提供的空闲连接数,后续任务就会排队等待连接,直接拉长总耗时;而多次await是串行执行,每次仅占用一个连接,不会触发连接池排队,耗时反而更短。 Task.WaitAll的阻塞特性:Task.WaitAll是同步阻塞调用,会让当前线程处于等待状态,无法被线程池复用;而await会释放当前线程回到线程池,后续任务调度更高效。如果运行环境是UI线程或旧版ASP.NET,这种阻塞还可能引发线程饥饿,进一步影响性能。- 线程调度开销:多任务并行时,线程池需要调度多个线程处理请求,会产生额外的上下文切换开销;串行
await复用线程池中的线程,上下文切换更少,开销更低。 - 测试代码的细节差异:注意到多次
await的代码里所有调用都是Method1,而Task.WaitAll版本调用了Method1到Method5。如果这些方法的实际逻辑(比如存储过程参数、执行时间)有差异,也可能影响结果,但从现象来看,连接池限制是最核心的原因。
内容的提问来源于stack exchange,提问作者Vivek Shukla
相关产品推荐
相关产品推荐

