如何让对象列表的计算在Task.WhenAll阶段异步并行执行?
解决CPU密集型任务的多线程并行执行问题
你的问题根源在于**Calculate方法是同步逻辑伪装成异步签名**,它没有真正的异步操作,直接在调用线程同步执行完所有计算后返回Task.CompletedTask,所以在遍历调用model.Calculate()时,所有计算已经在Main线程跑完了,Task.WhenAll自然没有实际等待的任务。
下面给出几种可行的实现方案:
方案1:用Task.Run包装同步计算(推荐)
先把Calculate改成明确的同步方法(因为是CPU密集型逻辑),然后在创建任务时用Task.Run将计算逻辑分配到线程池线程执行:
using System; using System.Threading.Tasks; using System.Collections.Generic; using System.Diagnostics; public class Program { public static async Task Main() { List<Model> models = new List<Model>(); for (int i = 100; i<10000; i++) { models.Add(new Model(i)); } List<Task> list = new(); var sw = new Stopwatch(); sw.Start(); foreach(var model in models) { // 用Task.Run将计算委托给线程池线程 list.Add(Task.Run(() => model.Calculate())); } Console.WriteLine($"创建任务耗时:{sw.ElapsedMilliseconds}ms"); sw.Restart(); await Task.WhenAll(list); Console.WriteLine($"并行计算总耗时:{sw.ElapsedMilliseconds}ms"); } } public class Model { public double Result {get; set;} public int input {get; set;} public Model(int Input) { input = Input; Result = 0; } // 同步CPU密集型计算方法 public void Calculate() { for (int i = 0; i < input; i++) { Result += i; } } }
方案2:修改Calculate方法为真正的异步包装
如果需要保持Calculate的异步方法签名,可以在方法内部用Task.Run包裹计算逻辑:
public class Model { // ... 其他成员不变 public Task Calculate() { // 将计算逻辑放到线程池执行,返回未完成的任务 return Task.Run(() => { for (int i = 0; i < input; i++) { Result += i; } }); } }
方案3:用Parallel.ForEach简化并行逻辑(.NET Core/.NET 5+推荐)
如果不需要返回Task集合,直接用Parallel.ForEach可以更简洁地实现CPU密集型任务的并行执行:
public static void Main() { List<Model> models = new List<Model>(); for (int i = 100; i<10000; i++) { models.Add(new Model(i)); } var sw = new Stopwatch(); sw.Start(); // 直接并行遍历执行计算 Parallel.ForEach(models, model => model.Calculate()); Console.WriteLine($"并行计算总耗时:{sw.ElapsedMilliseconds}ms"); }
关键注意点
- 不要用
return Task.CompletedTask来伪装异步方法,这会导致同步执行,完全失去并行意义。 - CPU密集型任务适合用线程池线程执行(
Task.Run/Parallel类),它们会自动利用多核CPU资源。 Task.WhenAll的作用是等待多个异步任务完成,前提是这些任务处于未完成状态,否则不会有任何等待效果。
内容的提问来源于stack exchange,提问作者rst
相关产品推荐
相关产品推荐

