开发的时候,要想让程序跑得快、响应及时,多线程是个绕不开的话题。.NET 给我们准备了一整套并发编程的工具,从最底层的 Thread 到高端的 async/await,想用哪种都行。不过多线程这玩意儿虽然好用,但也容易出幺蛾子:竞态条件、死锁、线程安全......一个不小心就踩坑。搞清楚每种并发机制适合干什么、怎么正确等待和同步,是写出靠谱并发程序的基本功。
.NET 多线程编程基础
线程是操作系统能调度的最小单位,一个进程里可以有好几个线程,它们共享进程的资源,但每个有自己的执行栈。在 .NET 里,线程分两种:前台线程会阻止进程退出,后台线程则随着主线程结束自动终止。用多线程的好处很明显:能把 CPU 用得更满、界面不卡、多核不浪费;但麻烦也不少:要操心线程安全、提防死锁、注意上下文切换的开销,调试起来也费劲①。
.NET 的线程模型是分层设计的:
-
Thread 类:直接操作线程,想怎么控制就怎么控制;
-
ThreadPool:线程池,省得老是创建销毁线程;
-
**Task Parallel Library (TPL)**:用任务(Task)来抽象并发操作,是现在的主流;
-
Parallel 类:专门用来并行处理数据,写起来简单;
-
BackgroundWorker:给 WinForms 和 WPF 准备的,做后台任务很方便;
-
async/await:基于状态机的异步编程范式,IO 密集型任务的首选②。
多线程任务实现方法详解
Thread 类:最底层的控制
System.Threading.Thread 是最原始的线程用法,适合那些需要长时间运行或者有特殊优先级要求的任务。用 Start() 启动,Join() 等着它干完,IsBackground 可以设成后台线程。好处是啥都能管,缺点是每次创建线程开销不小,而且没有任务组合、异常传播这些高级功能③。
var thread = new Thread(() =>
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"工作线程: {i}");
Thread.Sleep(200);
}
});
thread.IsBackground = true;
thread.Start();
thread.Join(); // 等它干完再继续
ThreadPool:轻量级任务
ThreadPool.QueueUserWorkItem 把任务丢给线程池,线程池自己会管线程的创建和销毁。特别适合那种短平快的活儿,比如写日志、更新缓存。但别把长时间运行的任务往里扔,不然会占着线程池的资源,而且线程池里的线程不能随便改名字、优先级这些属性④。
ThreadPool.QueueUserWorkItem(_ =>
{
Console.WriteLine("线程池任务执行");
Thread.Sleep(1000);
});
Task Parallel Library (TPL):现在的主流选择
Task 代表一个异步操作,用 Task.Run() 就能快速启动一个任务。它支持 WaitAll/WaitAny 来组合等待,可以用 ContinueWith 串起后续操作,还能用 CancellationToken 取消任务,出错了会给你一个 AggregateException 告诉你一堆异常。跟 Thread 比起来,Task 更轻量,组合起来也更灵活,而且和 async/await 配合得天衣无缝,已经是 .NET 并发编程的核心了⑤。
var task1 = Task.Run(() => { /* 干点活 */ });
var task2 = Task.Run(() => { /* 干点别的 */ });
await Task.WhenAll(task1, task2); // 异步等着,不堵线程
Parallel 类:数据并行好帮手
Parallel.For 和 Parallel.ForEach 会把循环里的活儿自动分到多个线程去干,内置了负载均衡和工作窃取。特别适合 CPU 密集型计算,比如处理图片、跑数值分析。但灵活度不高,不适合那种需要精细控制的任务流⑥。
Parallel.For(0, 100, i =>
{
ProcessData(i); // 自动分配给多个线程执行
});
BackgroundWorker:UI 场景专用
这个组件专门给 WinForms 和 WPF 设计的,通过 DoWork(后台干活)、ProgressChanged(更新 UI 进度)、RunWorkerCompleted(干完活通知)这些事件,简化了后台操作的写法。它会自动把事件封送到 UI 线程,省得你手动写 Invoke。不过自从 async/await 普及以后,用它的越来越少了⑦。
var worker = new BackgroundWorker();
worker.DoWork += (_, e) => { /* 后台任务 */ };
worker.RunWorkerCompleted += (_, e) => { /* 更新界面 */ };
worker.RunWorkerAsync();
async/await:异步编程的终极范式
async/await 让异步代码写起来就跟同步的一样,编译器在背后帮你生成状态机。特别适合 I/O 密集型操作,比如发 HTTP 请求、读写文件,用了它能大幅提升吞吐量。用的时候有几点要注意:用 ConfigureAwait(false) 避免不必要的上下文捕获,考虑用 ValueTask 减少内存分配,记得加上 CancellationToken 支持取消操作⑧。
async Task<string> FetchDataAsync()
{
using var client = new HttpClient();
return await client.GetStringAsync("https://api.example.com/data")
.ConfigureAwait(false);
}
线程等待与同步机制
基本等待方式
-
Thread.Join():让当前线程等着,直到目标线程结束; -
Task.Wait()/Task.WaitAll():同步等着任务完成; -
await Task.WhenAll():异步等好几个任务,不堵线程⑨。
同步原语
-
lock(Monitor):最简单的互斥锁,保证同一时间只有一个线程进临界区;
-
SemaphoreSlim:限制同时访问的线程数,支持异步等待;
-
ManualResetEvent:线程之间发信号用;
-
Barrier:让多个线程在某个阶段同步一下;
-
ReaderWriterLockSlim:允许多个线程同时读,但写的时候只能有一个,适合读多写少的场景⑩。
// lock 的例子
private readonly object _lock = new();
lock (_lock) { /* 临界区代码 */ }// SemaphoreSlim 的例子
private readonly SemaphoreSlim _semaphore = new(3);
await _semaphore.WaitAsync();
try { /* 最多允许3个线程同时进来 */ }
finally { _semaphore.Release(); }
超时处理
所有等待操作最好都设个超时,免得死锁:
-
Thread.Join(TimeSpan) -
Task.Wait(TimeSpan) -
await Task.WhenAny(task, Task.Delay(timeout)) -
Monitor.TryEnter(lockObj, TimeSpan)
高级主题与最佳实践
线程安全原则
-
能不共享就不共享:优先设计成无状态组件;
-
对象尽量不可变:共享的数据最好只读;
-
锁的范围要小:只锁必要的代码,别锁一大片;
-
选对集合 :单线程用
List<T>,多线程用ConcurrentQueue<T>、ConcurrentDictionary<TKey, TValue>这些线程安全的集合⑪。
死锁怎么防
死锁就是几个线程互相等对方手里的资源,结果都卡住了。预防方法有:
-
固定的锁顺序:所有线程拿锁的顺序都一样;
-
设超时 :用
Monitor.TryEnter,等不到就算了; -
别嵌套锁:尽量重构代码,减少锁依赖⑫。
性能优化
-
减少锁竞争:临界区越小越好,用细粒度锁;
-
并行度要合理 :
MaxDegreeOfParallelism设成Environment.ProcessorCount就行; -
I/O 操作用异步:别让网络、磁盘读写占着线程池;
-
先测量再优化 :拿
Stopwatch或性能分析器跑一跑,别瞎猜⑬。
实际应用场景
高性能日志处理器
用生产者-消费者模式:主线程只管往里加日志,后台一个 Task 不停地从队列里取出来写文件。用 BlockingCollection 保证线程安全,用 CancellationToken 实现优雅关闭⑭。
var queue = new BlockingCollection<string>();
Task.Run(async () =>
{
foreach (var msg in queue.GetConsumingEnumerable())
await File.AppendAllTextAsync("log.txt", msg);
});
queue.Add("日志消息");
并行数据处理管道
可以混着用:PLINQ 负责加载数据,Task.WhenAll 做 CPU 计算,Parallel.ForEach 做验证过滤。每个阶段选最合适的并发模型,把资源利用率拉满⑮。
实时仪表板(WPF)
用 async/await 从多个数据源拉数据,用 Dispatcher.InvokeAsync 在 UI 线程上更新控件,用 Task.WhenAny 配合 Task.Delay 实现超时保护,保证界面一直能响应⑯。
结论
.NET 的并发工具链从 Thread 到 async/await 一应俱全,怎么选看任务类型:
-
I/O 密集型 →
async/await; -
CPU 密集型 →
Parallel或 PLINQ; -
短期的后台任务 →
Task.Run或ThreadPool; -
长时间运行的高优先级任务 →
Thread。
不管用哪种模型,正确性永远比性能重要。同步机制要仔细设计,超时和取消必须考虑进去。把这些技术用好、组合好,才能写出既快又稳的并发系统。
并发模型关系图

这张图告诉我们:
-
不管哪种并发模型,最后都得跑在操作系统线程上;
-
Thread和ThreadPool是最底层的; -
Task是现代并发的核心,Parallel是它的特化版本; -
BackgroundWorker和async/await是针对特定场景(UI、I/O)的高层封装; -
箭头方向表示依赖和演进关系,越靠右的越推荐在新项目里用。
参考资料
① Microsoft. Threading in C# . https://learn.microsoft.com/en-us/dotnet/csharp/threading/
② Microsoft. Task Parallel Library (TPL) . https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl
③ Microsoft. Thread Class . https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread
④ Microsoft. ThreadPool Class . https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool
⑤ Stephen Toub. The Task-Based Asynchronous Pattern . https://devblogs.microsoft.com/pfxteam/tag/tap/
⑥ Microsoft. Parallel Class . https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel
⑦ Microsoft. BackgroundWorker Component . https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker
⑧ Microsoft. Asynchronous Programming with async and await . https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
⑨ Microsoft. Task.WaitAll Method . https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.waitall
⑩ Microsoft. Synchronization Primitives . https://learn.microsoft.com/en-us/dotnet/standard/threading/overview-of-synchronization-primitives
⑪ Microsoft. Thread-Safe Collections . https://learn.microsoft.com/en-us/dotnet/standard/collections/thread-safe/
⑫ Joe Duffy. Concurrent Programming on Windows . Addison-Wesley, 2008.
⑬ Ben Watson. Writing High-Performance .NET Code . 2nd ed., 2018.
⑭ Microsoft. BlockingCollectionClass . https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.blockingcollection-1
⑮ Microsoft. PLINQ. https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/parallel-linq-plinq