写软件的时候,要想让程序跑得快、响应及时,多线程是个绕不开的话题。.NET 给我们准备了一整套并发编程的工具,从最底层的 Thread 到高端的 async/await,想用哪种都行。不过多线程这玩意儿虽然好用,但也容易出幺蛾子:竞态条件、死锁、线程安全……一个不小心就踩坑。搞清楚每种并发机制适合干什么、怎么正确等待和同步,是写出靠谱并发程序的基本功。
.NET 多线程编程基础
线程是操作系统能调度的最小单位,一个进程里可以有好几个线程,它们共享进程的资源,但每个有自己的执行栈。在 .NET 里,线程分两种:前台线程会阻止进程退出,后台线程则随着主线程结束自动终止。用多线程的好处很明显:能把 CPU 用得更满、界面不卡、多核不浪费;但麻烦也不少:要操心线程安全、提防死锁、注意上下文切换的开销,调试起来也费劲①。
.NET 的线程模型是分层设计的:
Thread 类:直接操作线程,想怎么控制就怎么控制;
ThreadPool:线程池,省得老是创建销毁线程;
**Task Parallel Library (TPL)**:用任务(Task)来抽象并发操作,是现在的主流;
Parallel 类:专门用来并行处理数据,写起来简单;
BackgroundWorker:给 WinForms 和 WPF 准备的,做后台任务很方便;
async/await:基于状态机的异步编程范式,IO 密集型任务的首选②。
System.Threading.Thread是最原始的线程用法,适合那些需要长时间运行或者有特殊优先级要求的任务。用 Start启动,Join等着它干完,IsBackground可以设成后台线程。好处是啥都能管,缺点是每次创建线程开销不小,而且没有任务组合、异常传播这些高级功能③。
varthread = newThread( =>
{
for(inti = 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 并发编程的核心了⑤。
vartask1 = Task.Run( => { /* 干点活 */});
vartask2 = Task.Run( => { /* 干点别的 */});
awaitTask.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普及以后,用它的越来越少了⑦。
varworker = newBackgroundWorker;
worker.DoWork += (_, e) => { /* 后台任务 */};
worker.RunWorkerCompleted += (_, e) => { /* 更新界面 */};
worker.RunWorkerAsync;
async/await:异步编程的终极范式
async/await让异步代码写起来就跟同步的一样,编译器在背后帮你生成状态机。特别适合 I/O 密集型操作,比如发 HTTP 请求、读写文件,用了它能大幅提升吞吐量。用的时候有几点要注意:用 ConfigureAwait(false)避免不必要的上下文捕获,考虑用 ValueTask减少内存分配,记得加上 CancellationToken支持取消操作⑧。
asyncTask<string> FetchDataAsync()
{
usingvarclient = newHttpClient;
returnawaitclient.GetStringAsync("https://api.example.com/data")
.ConfigureAwait(false);
}
线程等待与同步机制基本等待方式
Thread.Join:让当前线程等着,直到目标线程结束;
Task.Wait/ Task.WaitAll:同步等着任务完成;
await Task.WhenAll:异步等好几个任务,不堵线程⑨。
lock(Monitor):最简单的互斥锁,保证同一时间只有一个线程进临界区;
SemaphoreSlim:限制同时访问的线程数,支持异步等待;
ManualResetEvent:线程之间发信号用;
Barrier:让多个线程在某个阶段同步一下;
ReaderWriterLockSlim:允许多个线程同时读,但写的时候只能有一个,适合读多写少的场景⑩。
privatereadonlyobject_lock = new;
lock(_lock) { /* 临界区代码 */}
// SemaphoreSlim 的例子
privatereadonlySemaphoreSlim _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 ,多线程用 ConcurrentQueue 、ConcurrentDictionary 这些线程安全的集合⑪。
死锁就是几个线程互相等对方手里的资源,结果都卡住了。预防方法有:
固定的锁顺序:所有线程拿锁的顺序都一样;
设超时:用 Monitor.TryEnter,等不到就算了;
别嵌套锁:尽量重构代码,减少锁依赖⑫。
减少锁竞争:临界区越小越好,用细粒度锁;
并行度要合理:MaxDegreeOfParallelism设成 Environment.ProcessorCount就行;
I/O 操作用异步:别让网络、磁盘读写占着线程池;
先测量再优化:拿 Stopwatch或性能分析器跑一跑,别瞎猜⑬。
用生产者-消费者模式:主线程只管往里加日志,后台一个 Task不停地从队列里取出来写文件。用 BlockingCollection保证线程安全,用 CancellationToken实现优雅关闭⑭。
varqueue = newBlockingCollection<string>;
Task.Run(async=>
{
foreach(varmsg inqueue.GetConsumingEnumerable)
awaitFile.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
课程简介:课程基于最新.NET 10与Microsoft Agent Framework (MAF)打造。覆盖.NET平台Al生态的核心知识点:M.E.Al、M.E.VD、MCP、Agent、A2A、Workflow、RAGAG-UI,全链路覆盖从能力搭建到多智能体编排与协作。返回搜狐,查看更多