C# Task 进阶:WaitAll / WaitAny / WhenAll / WhenAny
一、Task 同步阻塞:WaitAll 与 WaitAny
1. 核心原理
WaitAll、WaitAny 属于同步阻塞方法,执行后会阻塞当前主线程,等待任务集合满足条件后,才继续执行后续代码。
Task.WaitAll():阻塞线程,等待集合中所有任务全部执行完毕,才放行后续代码
Task.WaitAny():阻塞线程,只要集合中任意一个任务执行完毕,立刻放行后续代码
案例中设置无限循环任务,用于直观测试:单个任务完成触发 WaitAny、永久无法全部完成触发 WaitAll 阻塞。
2. 独立完整代码
private void button1_Click(object sender, EventArgs e) { TaskFactory factory = new TaskFactory(); List<Task> taskList = new List<Task>(); // 添加三个异步任务,第三个任务无限循环永不结束 taskList.Add(factory.StartNew(() => { Console.WriteLine("查询数据库"); })); taskList.Add(factory.StartNew(() => { Console.WriteLine("查询缓存"); })); taskList.Add(factory.StartNew(() => { while (true) { } Console.WriteLine("查询接口"); })); // 同步阻塞:任意一个任务完成即放行 Task.WaitAny(taskList.ToArray()); // 所有任务满足条件后执行 Console.WriteLine("至少一个操作执行完毕,主线程放行"); }3. 注意事项
WaitAll、WaitAny 为同步方法,会直接阻塞UI主线程,造成窗体卡死,WinForm 项目慎用
存在永久执行的任务时,WaitAll 会永久死锁阻塞,永远无法放行
WaitAny 只判断首个完成的任务,不关心剩余任务执行状态
适用于控制台程序同步等待,不推荐窗体UI线程直接调用
二、Task 异步回调:WhenAll 与 WhenAny
1. 核心原理
WhenAll、WhenAny 属于异步非阻塞方法,不会阻塞主线程,主线程代码会直接向下执行。需搭配ContinueWith实现任务完成后的回调逻辑。
Task.WhenAll():所有任务执行完毕后,触发回调方法
Task.WhenAny():任意一个任务执行完毕后,立刻触发回调方法
功能和 WaitAll/WaitAny 一致,核心区别为异步不阻塞UI,是项目开发主流用法。
2. 独立完整代码
private void button2_Click(object sender, EventArgs e) { TaskFactory factory = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(factory.StartNew(() => { Console.WriteLine("查询数据库"); })); taskList.Add(factory.StartNew(() => { Console.WriteLine("查询缓存"); })); taskList.Add(factory.StartNew(() => { while (true) { } Console.WriteLine("查询接口"); })); // 异步非阻塞:任意任务完成后触发回调 Task.WhenAny(taskList.ToArray()).ContinueWith(t => { Console.WriteLine("至少一个异步操作执行完了"); }); // 主线程不阻塞,直接执行 Console.WriteLine("主线程继续执行,不卡顿"); }3. 注意事项
When 系列方法异步无阻塞,完美解决UI卡死问题,优先用于WinForm/WPF项目
必须搭配 ContinueWith 使用,否则无法捕获任务完成事件
主线程代码执行顺序优先于回调代码
存在死循环任务时,WhenAll 回调永远不会触发
三、TaskFactory 自带回调:ContinueWhenAll / ContinueWhenAny
1. 核心原理
ContinueWhenAll、ContinueWhenAny是任务工厂专属方法,属于语法糖,等价于 WhenAll/WhenAny + ContinueWith 的组合写法,代码更简洁。
ContinueWhenAll:所有任务完成后自动执行回调
ContinueWhenAny:任意一个任务完成后自动执行回调
2. 独立完整代码
private void button3_Click(object sender, EventArgs e) { TaskFactory factory = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(factory.StartNew(() => { Console.WriteLine("查询数据库"); })); taskList.Add(factory.StartNew(() => { Console.WriteLine("查询缓存"); })); taskList.Add(factory.StartNew(() => { while (true) { } Console.WriteLine("查询接口"); })); // 工厂自带回调:任意任务完成触发 factory.ContinueWhenAny(taskList.ToArray(), (t) => { Console.WriteLine("至少一个操作执行完了"); }); }3. 注意事项
无需手动写 WhenAny/WhenAll 和 ContinueWith,封装度更高
仅 TaskFactory 创建的任务集合推荐使用该写法
本质仍是异步回调,不会阻塞主线程
四、Task 单个任务限时等待 Wait(int 毫秒)
1. 核心原理
单个 Task 自带Wait(毫秒)重载方法,作用是限时同步阻塞等待任务执行。等待时间内任务完成则直接放行,超时则主动结束等待,继续执行后续代码。
2. 独立完整代码
private void button4_Click(object sender, EventArgs e) { Task t1 = new Task(() => { Console.WriteLine("任务执行中"); Thread.Sleep(2000); // 任务耗时2秒 }); t1.Start(); // 限时等待1秒,超时自动放行 t1.Wait(1000); Console.WriteLine("等待结束,执行后续代码"); }3. 注意事项
属于同步阻塞方法,限时期间会卡住UI线程
可用于控制任务最大执行时长,防止任务无限阻塞程序
超时后任务不会终止,仍在后台继续执行,只是主线程不再等待
五、核心知识点终极对比总结
同步阻塞(卡死UI):WaitAll、WaitAny、Task.Wait(毫秒),适合控制台、后台同步逻辑
异步非阻塞(推荐):WhenAll、WhenAny + ContinueWith,适合WinForm界面开发
工厂简写:ContinueWhenAll/ContinueWhenAny,简化异步回调代码
核心区别:Wait系列阻塞主线程,When系列不阻塞,仅通过回调处理任务结果
六、Task 同步/异步等待方法终极对比表
汇总所有等待方法的核心差异,清晰区分阻塞特性、用法、场景,为考试和开发高频考点。
方法分组 | 具体方法 | 阻塞 | 核心功能 | 配套写法 | 适用场景 | 核心特点 |
同步阻塞 | WaitAll | 是 | 等待全部任务完成放行 | 直接调用 | 控制台同步批量任务 | 顺序可控,卡死UI;死循环会永久阻塞 |
WaitAny | 是 | 等待任意一个任务完成放行 | 直接调用 | 多任务快速响应 | 响应快,阻塞UI,忽略剩余任务 | |
Wait(毫秒) | 是 | 单个任务限时等待、超时放行 | 绑定单个Task | 限制任务最大时长 | 可超时管控,超时任务后台继续运行 | |
异步非阻塞 | WhenAll | 否 | 全部任务完成触发回调 | 搭配ContinueWith | WinForm批量异步收尾 | UI流畅,需回调处理结果 |
WhenAny | 否 | 任意任务完成触发回调 | 搭配ContinueWith | 多任务异步择优回调 | 高效不阻塞,需手动处理未完成任务 | |
ContinueWhen 系列 | 否 | 封装When+回调,一键执行 | TaskFactory专属 | 工厂任务简洁开发 | 代码极简,仅适配工厂创建任务 |
表格核心总结(必背考点)
阻塞核心:Wait 系列全为同步阻塞,卡死UI;When 系列全为异步非阻塞,界面流畅
功能等价:WaitAll ↔ WhenAll、WaitAny ↔ WhenAny,仅阻塞特性不同
开发首选:窗体程序优先使用 When 系列,控制台可使用 Wait 系列
语法糖:ContinueWhen 系列是 When+ContinueWith 的简化写法,专为任务工厂设计