Task.WaitAll与await Task.WhenAll对比及线程资源疑问
关于Task.WaitAll与await Task.WhenAll的疑问
我原本认为:执行Task.WaitAll时,Main方法所在线程会处于忙碌等待状态,系统仅能为其余任务提供n-1个线程(n为系统最大CPU线程数);而使用await Task.WhenAll时,Main方法所在线程不会被占用,可提供n个线程给其余任务。但实际测试中二者无差异。
请问:在此示例中,使用await Task.WhenAll的优势是否在于无需系统耗费资源创建新的“软件线程”?
示例代码:
int Tasks = Environment.ProcessorCount * 2; int Count = 0; List<Task> MyListForTask = new List<Task>(); void MyMethod() { lock (MyListForTask) { Count++; } Console.WriteLine(Count); int Sum = int.MaxValue; while (Sum > 0) { Sum--; } } //Option 1: Task.WaitAll. For a machine with 16 threads: 16 + 16 runs for (int i = 0; i < Tasks; i++) { MyListForTask.Add(new Task(MyMethod)); MyListForTask[i].Start(); } Console.WriteLine("Method Main works"); Task.WaitAll(MyListForTask.ToArray()); Console.WriteLine("\n"); MyListForTask.Clear(); Count = 0; //Option 2: await Task.WhenAll. For a machine with 16 threads: 16 + 16 runs for (int i = 0; i < Tasks; i++) { MyListForTask.Add(new Task(MyMethod)); MyListForTask[i].Start(); } Console.WriteLine("Method Main works"); await Task.WhenAll(MyListForTask.ToArray());
解答
首先要明确:你的测试场景中,MyMethod是纯CPU密集型的同步代码,且所有任务都是通过new Task(...).Start()手动启动,默认会被调度到线程池的工作线程上。
为什么测试中两者无差异?
- 调用
Task.WaitAll时,Main线程确实会进入阻塞状态,但线程池会根据负载动态扩容。因为你启动了2n个CPU密集任务,线程池会逐步创建足够的线程(甚至超过n)来处理任务,所以哪怕Main线程被占用,线程池仍会补充新线程执行剩余任务,整体并行度不会明显下降。 - 使用
await Task.WhenAll时,Main线程会在await点释放(控制台程序中靠异步Main的机制实现),但你的任务已经提前启动并调度到线程池,线程池依然会用相同的策略处理这些CPU密集任务,因此整体执行时间差异不大。
这个场景下await Task.WhenAll的优势是什么?
它的优势并非避免创建软件线程,而是:
- 避免线程资源浪费:
Task.WaitAll会让Main线程全程阻塞,等待期间无法处理任何其他工作,完全浪费了一个线程的资源;而await会释放该线程,让它可以被线程池拿去处理其他任务(如果存在的话)。 - 符合非阻塞异步模型:在UI程序、Web服务等场景中,阻塞主线程会导致界面卡死、请求无法响应,
await的非阻塞特性会让程序响应性更好——只是控制台程序中这个表现不突出。 - 异常处理更简洁:
await会直接抛出任务中的异常,无需额外处理AggregateException;而Task.WaitAll若有任务抛出异常,会封装成AggregateException抛出,需要额外拆解处理。
另外补充:代码中new Task(MyMethod).Start()是较旧的写法,更推荐使用Task.Run(MyMethod),它会直接将任务调度到线程池,写法更简洁直观。
内容的提问来源于stack exchange,提问作者user20445618
相关产品推荐
相关产品推荐

