绝大多数新项目应优先使用 Task 而非 Thread;仅在需操作系统级线程控制(如 STA 线程、自定义调度)时才用 Thread,且须避免用 Thread 处理 I/O 或未捕获异常。
Thread,而不是 Task
绝大多数新项目里,你根本不需要手动创建 Thread。它只在极少数需要“操作系统级线程控制”的场景下才合理:
ApartmentState.STA、Priority 或 ProcessorAffinity 等底层属性⚠️ 常见错误:用 new Thread(() => { ... }).Start() 处理 HTTP 请求或数据库查询——这会快速耗尽系统资源,还无法捕获异常,极易导致进程崩溃。
Task.Run 是默认选择,但不是万能钥匙Task.Run 本质是把工作丢给线程池,它省心、高效、可 await、可组合。但它不等于“异步”本身:
HttpClient.GetAsync、FileStream.ReadAsync),优先用原生 async 方法,别包一层 Task.Run —— 否则只是把 I/O 等待强行塞进线程池线程,浪费线程资源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),极易拖垮线程池:
async 方法 + Task.WhenAll
Task.Run + Task.WhenAll,但要考虑线程池饥饿风险(可配合 TaskCreationOptions.LongRunning 或限流)Task.Run
最容易被忽略的一点:线程池线程不是“无限供应”的。盲目用 Task.Run 替代 Thread,可能把线程池撑爆,导致后续所有 Task.Run、Timer、甚至 async 回调都排队等待——这时连 UI 都卡死,却查不到明显异常。