绝大多数新项目应优先使用 Task 而非 Thread;仅在需操作系统级线程控制(如 STA 线程、自定义调度)时才用 Thread,且须避免用 Thread 处理 I/O 或未捕获异常。

什么时候该用 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.WhenAll - CPU 密集 → 用
Task.Run+Task.WhenAll,但要考虑线程池饥饿风险(可配合TaskCreationOptions.LongRunning或限流) - 混合场景 → 分离 I/O 和 CPU 阶段,I/O 阶段不占线程,CPU 阶段再
Task.Run
最容易被忽略的一点:线程池线程不是“无限供应”的。盲目用 Task.Run 替代 Thread,可能把线程池撑爆,导致后续所有 Task.Run、Timer、甚至 async 回调都排队等待——这时连 UI 都卡死,却查不到明显异常。







