发布于2026-07-20 阅读(0)
扫一扫,手机访问
先问一个问题:你还在用 Task.WaitAll 吗?还在循环里无脑地 Task.Run 吗?如果你在真实服务中这么干,那线程池饥饿、响应延迟飙升、异常静默这些坑,早晚会踩上一个。
别觉得这是危言耸听。这两类写法,是很多人在高并发场景下遇到性能瓶颈的直接原因。
关键看任务性质——这是判断的唯一标准。CPU 密集型任务,才适合扔给线程池;IO 密集型任务,包一层 Task.Run 只是自欺欺人。
Task.Run 是合理的,但必须控制并发数。通常的做法是用 SemaphoreSlim 限流,避免一下子开几百个任务把线程池拖垮。HttpClient.GetAsync、File.ReadAllTextAsync):直接调原生的 *Async 方法即可。千万不要写 Task.Run(() => File.ReadAllText(...))——这种写法会占用线程池线程干等,不是异步,是“伪异步”。await File.ReadAllTextAsync(...) 负责 IO,Task.Run(() => ValidateAndCompute(...)) 负责计算,分而治之。怎么判断自己是不是踩坑了?观察几个信号:ThreadPool.GetA vailableThreads 返回 0、HTTP 请求超时陡增、日志里看不到异常但接口卡死——这些现象出现时,多半是 Task.Run 用错了地方。
Task.WaitAll 是同步阻塞调用,在 ASP.NET Core 或 WinForms 等有同步上下文的环境里,轻则性能归零,重则死锁。这可不是危言耸听。
await Task.WhenAll(tasks) 返回 Task,结果按传入顺序排列,可以直接遍历,干净利落。Task.WaitAll(tasks) 返回 void,你还得手动遍历每个 task.Result,而且一旦某个任务失败,整个调用会抛出 AggregateException,不拆包你根本不知道哪个出错了。var t1 = TryGetUserAsync().ContinueWith(t => t.Exception?.ToString() ?? "OK"),而不是等 WhenAll 完了再统一 try/catch。await Task.WhenAll(batch) 处理,避免调度器状态机开销过大。这个方法的默认行为有点“坑”:它不捕获同步上下文,也不支持 async lambda 的自动展平。如果你写 Task.Factory.StartNew(async () => await DoWork()),得到的是 Task,不是 Task。用 await 去等它,不仅会卡住,异常还会被吞掉。
Task.Run。它内部做了正确封装,支持 async lambda 并自动 Unwrap,省心很多。LongRunning 或 PreferFairness 选项时,才考虑 Task.Factory.StartNew,而且必须显式指定 TaskScheduler.Default。task.IsFaulted == true 却查不到任何异常堆栈。遇到这类情况,先检查是不是用了 Task.Factory.StartNew。最后说一个容易被忽略的观点:并行不等于更快。如果所有任务都在争抢同一个锁、共用一个数据库连接池、或反复触发 GC,加再多线程也白搭。先压测,再分析瓶颈,比盲目上 Task.Run 有用得多。

售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8