什么时候该用 Thread
,而不是 Task
绝大多数新项目里,你根本不需要手动创建
Thread。它只在极少数需要“操作系统级线程控制”的场景下才合理: 必须长期独占一个线程,且生命周期和主线程不一致(比如 Windows Forms 中的 STA 线程需承载 COM 组件) 需要设置
ApartmentState.STA、
Priority或
ProcessorAffinity等底层属性 实现自定义调度器或实时性极高的嵌入式/高频交易逻辑(此时甚至可能绕过 .NET,直接调用 Win32 API)
⚠️ 常见错误:用
new Thread(() => { ... }).Start() 处理 HTTP 请求或数据库查询——这会快速耗尽系统资源,还无法捕获异常,极易导致进程崩溃。
Task.Run
是默认选择,但不是万能钥匙
Task.Run本质是把工作丢给线程池,它省心、高效、可 await、可组合。但它不等于“异步”本身: 如果工作是 I/O 密集型(如
HttpClient.GetAsync、
FileStream.ReadAsync),优先用原生
async方法,别包一层
Task.Run—— 否则只是把 I/O 等待强行塞进线程池线程,浪费线程资源 如果工作是 CPU 密集型(如图像处理、加密计算),
Task.Run才是正确选择,它让 CPU 工作不阻塞主线程
Task.Run返回的是
Task,不是线程;你无法通过它获取或控制底层线程 ID、栈大小等信息
await Task.Run(() => ComputeFibonacci(40)); // ✅ 合理:CPU 密集
await Task.Run(() => File.ReadAllText("data.txt")); // ❌ 不推荐:应改用 File.ReadAllTextAsync
异常处理差异大,一不小心就崩
Thread抛出未捕获异常会直接终止整个进程(.NET 6+ 默认行为),而
Task的异常会被“封印”在
Task对象里,直到你访问
Result、
Wait()或
await它——这时才真正抛出: 用
Thread:异常必须在线程内部
try/catch,否则无处拦截 用
Task:可以在调用侧统一
try/catch,甚至用
task.Exception查看所有子任务异常 注意:
Task.Run(() => { throw new Exception(); }) 不会立刻崩溃,但 await task或
task.Wait()就会触发
别混淆“并发”和“并行”,选错模型性能反而更差
如果你要同时发起 100 个 HTTP 请求,用
Task.WhenAll+
async方法是并发(concurrency),靠 I/O 完成端口,几乎不占线程;而用
Task.Run包 100 次,就是硬拉 100 个线程池线程去等响应——这是伪并行(parallelism),极易拖垮线程池: I/O 密集 → 用原生
async方法 +
Task.WhenAllCPU 密集 → 用
Task.Run+
Task.WhenAll,但要考虑线程池饥饿风险(可配合
TaskCreationOptions.LongRunning或限流) 混合场景 → 分离 I/O 和 CPU 阶段,I/O 阶段不占线程,CPU 阶段再
Task.Run
最容易被忽略的一点:线程池线程不是“无限供应”的。盲目用
Task.Run替代
Thread,可能把线程池撑爆,导致后续所有
Task.Run、
Timer、甚至
async回调都排队等待——这时连 UI 都卡死,却查不到明显异常。
