为什么需要多线程
C# 多线程编程的价值主要体现在两点:一是让耗时操作不阻塞主线程,二是利用多核 CPU 提升吞吐。桌面程序中,网络请求或文件读写放在 UI 线程会直接卡顿;服务端程序中,合理的并发能显著缩短响应时间。但线程不是越多越好,上下文切换和锁竞争都会带来额外开销。
从 Thread 到 Task 的演进
Thread 是最基础的抽象,适合需要长期运行、独占线程的场景。它的缺点是创建成本高、管理麻烦。线程池通过复用线程降低了开销,适合大量短任务。而 Task 建立在调度器之上,支持返回值、延续和异常聚合,是现代 C# 并发的首选。
var task = Task.Run(() => Compute());
int result = await task;
async/await 并不等于“新开线程”,它的核心是把等待变成非阻塞的挂起。I/O 密集型操作应优先使用异步 API,而不是用 Task.Run 包一层同步调用。
线程安全与同步原语
多个线程访问共享可变状态时,必须考虑线程安全。常用手段包括:
lock:简单可靠,适合保护短小的临界区,避免在锁内做耗时操作。Interlocked:用于计数、标志位等原子操作,性能更好。SemaphoreSlim:限制并发数量,其异步版本适合在async方法中等待。- 并发集合:
ConcurrentDictionary、ConcurrentQueue等减少了手写锁的需求。
原则是尽量共享不可变数据,或让每个线程持有独立状态,从设计上减少同步需求。
常见陷阱与规避
死锁多源于锁顺序不一致或同步等待异步结果。统一加锁顺序、避免在持锁时调用外部代码,能大幅降低风险。另一个常见问题是吞掉异常:Task 的异常在 await 时才重新抛出,忘记等待就会静默失败。此外,不要用 Thread.Sleep 代替异步等待,也不要随意使用 Task.Result,它可能在有同步上下文时造成阻塞。
实践建议
先用性能分析确认瓶颈是否真在并发,再决定引入多线程。优先选择 Task 与异步 API,控制共享状态范围,对关键路径补充取消令牌 CancellationToken,让任务可被及时终止。写并发代码时,正确性优先于技巧,可测试、可读的同步方案往往比“聪明”的无锁实现更可靠。