应优先使用 task 而非 thread 实现并发,因 thread 易导致进程静默退出、异常丢失、上下文失效;task.run 基于线程池、支持 async/await、异常传播与上下文捕获;cpu 密集型用 task.run,i/o 密集型应直接 await;task.whenall 实现异步并行等待,避免 wait() 阻塞。

别用 Thread 做常规并发,除非你真在写线程池调度器或需要独占线程。 现代 C# 中,99% 的“同时做几件事”需求,都应该用 Task,而不是手动 new 一个 Thread。
为什么 Thread.Start() 在 ASP.NET Core 或 WinForms 里容易静默失败
常见错误现象:Thread 启动后主线程(比如 HTTP 请求线程或 UI 线程)一结束,整个进程就退出,子线程根本没跑完;或者子线程抛了异常,主线程完全收不到,程序崩溃但没堆栈、没日志。
根本原因有三个:
-
Thread默认是前台线程,主线程退出 = 进程强制终止,不管子线程死活 - 异常不会自动跨线程传播,
try/catch包不住另一个线程里的throw -
HttpContext、AsyncLocal<t></t>、UI 控件引用等上下文,在新线程里全为空——你拿不到当前请求信息,也更新不了界面
如果你非得用 Thread,至少得写成这样(但依然不推荐):
var t = new Thread(() => {
try { DoWork(); }
catch (Exception ex) { Log.Error(ex); }
});
t.IsBackground = true; // 防止阻塞进程退出
t.Start();
Task.Run 是什么,它和 Thread 到底差在哪
Task.Run 不是“启动一个新线程”,而是把工作交给 ThreadPool,由线程池里空闲的线程来执行。它复用已有线程,避免了每次创建/销毁线程的开销(默认栈 1MB),也天然支持异常捕获、等待、延续、取消等能力。
关键差异点:
-
Thread→ 操作系统级资源,一对一绑定内核线程;Task.Run→ CLR 托管任务,一对多映射到线程池线程 -
Thread不认识async/await,Task.Run(async () => ...)会提前返回,根本等不到 await 完成 -
Task.Run自动捕获同步上下文(比如 WPF 的 Dispatcher),Thread完全不感知 -
Task.Run支持ConfigureAwait(false),能避免不必要的上下文切换开销
正确写法(CPU 密集型):
var task = Task.Run(() => HeavyComputation()); await task; // 安全等待,异常可被外层 catch
错误写法(I/O 密集型,纯浪费):
// ❌ 别这么干:用 Task.Run 包装 await,徒增线程切换 Task.Run(async () => await File.ReadAllTextAsync(path));
什么时候必须用 Task.Factory.StartNew 而不是 Task.Run
绝大多数情况不需要。只有两个真实场景会用到 Task.Factory.StartNew:
- 你需要标记任务为
TaskCreationOptions.LongRunning:告诉线程池“这活儿会长时间占用线程(比如 >500ms),别塞进普通队列,给我单独起个线程”,避免阻塞其他短任务 - 你需要传入自定义
TaskScheduler:比如 WPF 中用TaskScheduler.FromCurrentSynchronizationContext()把延续操作切回 UI 线程
注意:Task.Factory.StartNew 默认不捕获同步上下文,而 Task.Run 会——这点在 UI 应用中非常关键。
示例(WPF 场景):
// ✅ 正确:延续操作回到 UI 线程更新控件
Task.Run(() => Compute()).ContinueWith(t =>
label.Content = t.Result,
TaskScheduler.FromCurrentSynchronizationContext());
<p>// ❌ 错误:ContinueWith 默认在 ThreadPool 线程执行,更新 UI 会报错
Task.Factory.StartNew(() => Compute()).ContinueWith(t => label.Content = t.Result);
</p>
Task.WhenAll 和多个 .Wait() 的区别在哪
这是最容易踩坑的性能点。用 .Wait() 或 .Result 是同步阻塞,会卡住当前线程;而 await Task.WhenAll(...) 是异步等待,线程可以去干别的事。
典型反模式:
// ❌ 卡死 UI 或请求线程,且异常处理混乱 task1.Wait(); task2.Wait(); task3.Wait();
正确做法:
- CPU 密集型并行:用
await Task.WhenAll(tasks),所有任务真正并行执行 - 需要按顺序等结果:用
await task1; await task2;,但注意这不是并行 - 想等任意一个完成就继续:用
await Task.WhenAny(tasks)
复杂点在于:如果其中一个 Task 抛异常,Task.WhenAll 会 aggregate 所有异常,你得用 try/catch 捕获 AggregateException,而不是单个异常——这点常被忽略。











