在 C# 开发中,线程与并发编程是提升程序性能、响应速度的核心技术,尤其适用于 UI 后台任务、批量处理、网络请求等场景。本文将系统梳理 C# 线程相关的所有核心知识点 ,包括基础概念、线程创建、同步机制、线程通信、线程池、Task 异步编程、高级工具类等,为所有代码添加详细注释,让你不仅能直接运行,还能理解每一步的原理,让你一篇文章吃透 C# 并发编程。
一、核心概念:线程与并发基础
在学习具体 API 前,先理清核心概念,避免理解偏差:
1. 线程与进程的关系
-
进程:操作系统分配资源的基本单位(如一个.exe 程序),进程间资源隔离(内存、文件句柄等)。
-
线程:进程内的执行单元,一个进程可包含多个线程,线程共享进程资源(如堆内存),但拥有独立的栈内存。
-
核心优势:多线程可让程序 "同时" 执行多个任务(如 UI 线程响应点击,后台线程下载文件),提升吞吐量和用户体验。
2. 线程的生命周期
C# 线程的生命周期完全遵循操作系统线程模型,关键阶段如下:
| 阶段 | 说明 |
|---|---|
| 新建(New) | 创建 Thread 对象,但未调用 Start(),线程未被操作系统调度。 |
| 就绪(Runnable) | 调用 Start() 后,线程等待 CPU 调度(此时已被操作系统纳入线程队列)。 |
| 运行(Running) | CPU 分配时间片,线程执行 ThreadStart 委托中的逻辑。 |
| 阻塞(Blocked) | 线程因等待资源暂停(如 Sleep、Wait、IO 操作),释放 CPU 时间片。 |
| 终止(Terminated) | 线程执行完毕、抛出未捕获异常,或被正常取消(非强制终止)。 |
3. 线程的分类
-
前台线程:默认类型,进程必须等待所有前台线程完成后才会退出(即使主线程结束)。
-
后台线程 :通过
Thread.IsBackground = true设置,进程无需等待后台线程完成,主线程结束后后台线程会被强制终止。
4. 并发与并行的区别
-
并发(Concurrency):多个线程 "交替" 使用 CPU(单核 CPU 也能实现),看似同时执行(如单 CPU 下的多任务切换)。
-
并行(Parallelism):多个线程 "同时" 执行(需多核 CPU 支持),真正的同时处理多个任务。
二、基础实现:Thread 类(传统线程)
Thread 类是 C# 最底层的线程操作 API,位于 System.Threading 命名空间,适合需要完全控制线程的场景(如设置优先级、后台线程)。
1. 线程的创建与启动
C# 中创建线程的核心是通过 ThreadStart 或 ParameterizedThreadStart 委托指定线程执行逻辑,支持 3 种常用方式:
方式 1:无参数线程(ThreadStart 委托)
cs
using System;
using System.Threading;
class ThreadDemo1
{
// 主线程入口
static void Main()
{
// 1. 创建 Thread 对象,传入无参数的线程执行逻辑(ThreadStart 委托)
// DoWork 是线程要执行的方法,无需参数
Thread thread = new Thread(DoWork);
// 2. 可选配置:设置为后台线程
// 后台线程特性:主线程结束后,后台线程会被强制终止(避免程序挂起)
thread.IsBackground = true;
// 3. 给线程命名(用于调试,在 VS 调试窗口可看到线程名称)
thread.Name = "无参数工作线程";
// 4. 启动线程:线程进入「就绪状态」,等待 CPU 调度
// 注意:Start() 只是触发调度,不会阻塞主线程
thread.Start();
// 主线程的逻辑(与子线程并行执行)
Console.WriteLine($"主线程({Thread.CurrentThread.Name})执行中...");
// 5. 主线程等待子线程完成(Join() 会阻塞主线程)
// 若不调用 Join(),主线程可能提前退出,导致后台线程被终止
thread.Join();
Console.WriteLine("主线程执行完毕");
}
/// <summary>
/// 无参数的线程执行方法(ThreadStart 委托对应的方法签名)
/// </summary>
static void DoWork()
{
// Thread.CurrentThread:获取当前正在执行的线程实例
Console.WriteLine($"子线程({Thread.CurrentThread.Name})开始执行");
// Thread.Sleep(2000):线程休眠 2000 毫秒(2 秒)
// 特性:释放 CPU 时间片,让其他线程执行,但不释放已持有的锁
Thread.Sleep(2000);
Console.WriteLine($"子线程({Thread.CurrentThread.Name})执行完毕");
}
}
方式 2:带参数线程(ParameterizedThreadStart 委托)
参数必须是 object 类型,需手动装箱拆箱:
cs
using System;
using System.Threading;
class ThreadDemo2
{
static void Main()
{
// 1. 创建 Thread 对象,传入带参数的执行逻辑(ParameterizedThreadStart 委托)
// DoWorkWithParam 方法必须接收 object 类型参数
Thread thread = new Thread(DoWorkWithParam);
thread.Name = "带参数工作线程";
thread.IsBackground = true;
// 2. 启动线程并传递参数:参数类型必须是 object(装箱)
thread.Start("这是传递给子线程的参数");
// 主线程等待子线程完成
thread.Join();
Console.WriteLine("主线程执行完毕");
}
/// <summary>
/// 带参数的线程执行方法(ParameterizedThreadStart 委托对应的签名)
/// </summary>
/// <param name="obj">接收的参数(object 类型,需手动拆箱)</param>
static void DoWorkWithParam(object obj)
{
// 拆箱:将 object 类型转为目标类型(此处是 string)
// 注意:若传递的参数类型与目标类型不匹配,会抛出 InvalidCastException
string param = (string)obj;
Console.WriteLine($"子线程({Thread.CurrentThread.Name})接收参数:{param}");
// 模拟任务耗时 1 秒
Thread.Sleep(1000);
Console.WriteLine($"子线程({Thread.CurrentThread.Name})执行完毕");
}
}
方式 3:Lambda 表达式(简洁写法)
无需单独定义方法,适合简单逻辑:
cs
using System;
using System.Threading;
class ThreadDemo3
{
static void Main()
{
// 1. 无参数 Lambda 线程
Thread thread1 = new Thread(() =>
{
// Lambda 表达式直接包含线程逻辑,无需单独定义方法
Console.WriteLine($"线程 1({Thread.CurrentThread.ManagedThreadId}):Lambda 无参数线程");
Thread.Sleep(800); // 模拟耗时
});
thread1.Name = "Lambda无参线程";
thread1.IsBackground = true;
thread1.Start();
// 2. 带参数 Lambda 线程(通过闭包传递参数,无需装箱拆箱)
string message = "Lambda 闭包参数"; // 闭包变量:子线程可访问主线程的局部变量
Thread thread2 = new Thread(() =>
{
// 注意:闭包变量的生命周期会被延长,直到线程执行完毕
Console.WriteLine($"线程 2({Thread.CurrentThread.ManagedThreadId}):接收闭包参数:{message}");
Thread.Sleep(1200);
});
thread2.Name = "Lambda带参线程";
thread2.IsBackground = true;
thread2.Start();
// 等待两个线程都完成
thread1.Join();
thread2.Join();
Console.WriteLine("所有子线程执行完毕,主线程退出");
}
}
2. Thread 类的关键方法与属性
| 方法 / 属性 | 作用说明 |
|---|---|
Start() |
启动线程,进入就绪状态(仅能调用一次)。 |
Join() |
阻塞当前线程,等待目标线程执行完毕。 |
Join(int milliseconds) |
超时等待(毫秒),超时后当前线程继续执行。 |
Sleep(int milliseconds) |
线程休眠指定时间(毫秒),释放 CPU 时间片,不释放锁。 |
Yield() |
主动放弃当前 CPU 时间片,让其他线程优先执行(仅在同优先级线程间)。 |
IsBackground |
属性:设置是否为后台线程(默认 false,前台线程)。 |
Name |
属性:设置线程名称(用于调试)。 |
Priority |
属性:设置线程优先级(ThreadPriority.Lowest ~ Highest,默认 Normal)。 |
IsAlive |
属性:判断线程是否处于运行 / 阻塞状态(未终止)。 |
Abort() |
强制终止线程(已过时!会抛出 ThreadAbortException,可能导致资源泄露,禁止使用)。 |
3. 线程优先级的注意事项
-
线程优先级仅为操作系统调度提供 "建议",不保证高优先级线程一定先执行。
-
避免设置过高优先级(如
Highest),可能导致低优先级线程 "饥饿"(无法获取 CPU 时间)。
三、并发安全:同步机制(解决资源竞争)
多线程共享资源时(如全局变量、数据库连接),会出现 "指令交错" 导致数据不一致(如超卖、余额错误),这种问题称为竞态条件(Race Condition)。同步机制的核心是保证 "临界区"(共享资源的操作代码)同一时间只有一个线程执行。
C# 提供多种同步工具,按易用性和场景分类如下:
1. lock 关键字(最常用)
lock 是 C# 最简洁的同步方式,底层基于 Monitor 类,语法糖封装,推荐优先使用。
核心规则:
-
锁对象必须是引用类型 (如
object、string,但不推荐用string常量,可能被池化复用)。 -
锁对象应设为
private static(静态资源)或private(实例资源),避免被外部修改。 -
临界区代码应尽量精简(仅包含共享资源操作),避免长时间阻塞。
示例:解决多线程累加问题
cs
using System;
using System.Threading;
class LockDemo
{
// 共享资源:多个线程会同时修改的变量(此处模拟计数器)
private static int _count = 0;
// 锁对象:用于控制临界区访问(必须是引用类型)
// 1. 用 private static 修饰:静态资源对应静态锁对象,避免外部访问
// 2. 不推荐用 string/this 作为锁对象:string 可能被池化复用,this 可能被外部引用
private static readonly object _lockObj = new object();
static void Main()
{
// 创建 10 个线程,同时执行累加操作
int threadCount = 10;
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++)
{
// 每个线程都执行 Increment 方法(修改共享资源 _count)
threads[i] = new Thread(Increment);
threads[i].Name = $"累加线程{i + 1}";
threads[i].IsBackground = true;
threads[i].Start();
}
// 等待所有线程完成(避免主线程提前退出)
foreach (var thread in threads)
{
thread.Join();
}
// 若不加锁,最终结果会小于 10000(因指令交错);加锁后正确输出 10000
Console.WriteLine($"最终累加结果:{_count}");
}
/// <summary>
/// 累加方法(修改共享资源 _count)
/// </summary>
static void Increment()
{
// 循环 1000 次,每次累加 1(模拟多次修改共享资源)
for (int i = 0; i < 1000; i++)
{
// lock 关键字:保证临界区代码同一时间只有一个线程执行
// 原理:进入 lock 时获取 _lockObj 的锁,退出时自动释放锁(即使抛异常)
lock (_lockObj)
{
// 临界区:操作共享资源的代码(必须精简,减少锁持有时间)
_count++;
// 注意:_count++ 实际是 3 步操作(读取→加1→写入),无锁时会交错
}
// 非临界区:无需同步的代码(尽量放在 lock 外部,提高并发效率)
// Thread.Sleep(1); // 模拟其他耗时操作
}
}
}
2. Monitor 类(更灵活的锁)
Monitor 是 lock 的底层实现,提供更精细的控制(如尝试获取锁、超时锁),语法比 lock 繁琐,但功能更强。
核心方法:
-
Enter(object obj):获取锁(若锁被占用,阻塞当前线程)。 -
TryEnter(object obj, int millisecondsTimeout):尝试获取锁,超时返回false(非阻塞)。 -
Exit(object obj):释放锁(必须与Enter成对出现,建议在finally中调用)。 -
Pulse()/PulseAll():唤醒等待队列中的线程(用于线程通信)。 -
Wait():释放锁,进入等待队列(用于线程通信)。
示例:超时获取锁
cs
using System;
using System.Threading;
class MonitorDemo
{
private static int _count = 0;
private static readonly object _lockObj = new object();
static void Main()
{
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++)
{
threads[i] = new Thread(IncrementWithMonitor);
threads[i].Name = $"Monitor线程{i + 1}";
threads[i].IsBackground = true;
threads[i].Start();
}
foreach (var thread in threads)
{
thread.Join();
}
Console.WriteLine($"最终累加结果:{_count}");
}
/// <summary>
/// 使用 Monitor 实现同步(支持超时获取锁)
/// </summary>
static void IncrementWithMonitor()
{
for (int i = 0; i < 1000; i++)
{
bool gotLock = false; // 标记是否成功获取锁
try
{
// Monitor.TryEnter:尝试获取锁,超时时间 100 毫秒
// 参数 1:锁对象;参数 2:超时时间(毫秒);返回值:是否获取成功
// 优势:避免无限等待,适合高并发场景(防止死锁)
gotLock = Monitor.TryEnter(_lockObj, 100);
if (gotLock)
{
// 成功获取锁,执行临界区代码
_count++;
}
else
{
// 获取锁超时,可执行降级逻辑(如记录日志、跳过本次操作)
Console.WriteLine($"线程 {Thread.CurrentThread.Name}:获取锁超时,跳过本次累加");
}
}
finally
{
// 关键:只有成功获取锁,才需要释放(避免抛 SynchronizationLockException)
if (gotLock)
{
Monitor.Exit(_lockObj); // 释放锁(必须在 finally 中,确保锁一定会释放)
}
}
}
}
}
3. 其他同步工具(按场景选择)
3.1 SemaphoreSlim(信号量:控制并发线程数)
用于限制同时访问临界区的线程数量(如限流、连接池控制),轻量级,支持异步等待。
cs
// 信号量:允许 3 个线程同时访问
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(3);
static void Main()
{
for (int i = 0; i < 10; i++)
{
new Thread(DoWorkWithSemaphore).Start(i);
}
}
static void DoWorkWithSemaphore(object id)
{
Console.WriteLine($"线程 {id} 等待进入临界区");
_semaphore.Wait(); // 获取信号量(若已达 3 个,阻塞)
try
{
Console.WriteLine($"线程 {id} 进入临界区,当前并发数:{3 - _semaphore.CurrentCount}");
Thread.Sleep(1000); // 模拟耗时操作
}
finally
{using System;
using System.Threading;
class MonitorDemo
{
private static int _count = 0;
private static readonly object _lockObj = new object();
static void Main()
{
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++)
{
threads[i] = new Thread(IncrementWithMonitor);
threads[i].Name = $"Monitor线程{i + 1}";
threads[i].IsBackground = true;
threads[i].Start();
}
foreach (var thread in threads)
{
thread.Join();
}
Console.WriteLine($"最终累加结果:{_count}");
}
/// <summary>
/// 使用 Monitor 实现同步(支持超时获取锁)
/// </summary>
static void IncrementWithMonitor()
{
for (int i = 0; i < 1000; i++)
{
bool gotLock = false; // 标记是否成功获取锁
try
{
// Monitor.TryEnter:尝试获取锁,超时时间 100 毫秒
// 参数 1:锁对象;参数 2:超时时间(毫秒);返回值:是否获取成功
// 优势:避免无限等待,适合高并发场景(防止死锁)
gotLock = Monitor.TryEnter(_lockObj, 100);
if (gotLock)
{
// 成功获取锁,执行临界区代码
_count++;
}
else
{
// 获取锁超时,可执行降级逻辑(如记录日志、跳过本次操作)
Console.WriteLine($"线程 {Thread.CurrentThread.Name}:获取锁超时,跳过本次累加");
}
}
finally
{
// 关键:只有成功获取锁,才需要释放(避免抛 SynchronizationLockException)
if (gotLock)
{
Monitor.Exit(_lockObj); // 释放锁(必须在 finally 中,确保锁一定会释放)
}
}
}
}
}
Console.WriteLine($"线程 {id} 退出临界区");
_semaphore.Release(); // 释放信号量
}
}
3.2 Mutex(互斥锁:跨进程同步)
与 lock 类似,但支持跨进程同步 (如多个程序访问同一文件),重量级,开销比 lock 大。
cs
using System;
using System.Threading;
class MonitorDemo
{
private static int _count = 0;
private static readonly object _lockObj = new object();
static void Main()
{
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++)
{
threads[i] = new Thread(IncrementWithMonitor);
threads[i].Name = $"Monitor线程{i + 1}";
threads[i].IsBackground = true;
threads[i].Start();
}
foreach (var thread in threads)
{
thread.Join();
}
Console.WriteLine($"最终累加结果:{_count}");
}
/// <summary>
/// 使用 Monitor 实现同步(支持超时获取锁)
/// </summary>
static void IncrementWithMonitor()
{
for (int i = 0; i < 1000; i++)
{
bool gotLock = false; // 标记是否成功获取锁
try
{
// Monitor.TryEnter:尝试获取锁,超时时间 100 毫秒
// 参数 1:锁对象;参数 2:超时时间(毫秒);返回值:是否获取成功
// 优势:避免无限等待,适合高并发场景(防止死锁)
gotLock = Monitor.TryEnter(_lockObj, 100);
if (gotLock)
{
// 成功获取锁,执行临界区代码
_count++;
}
else
{
// 获取锁超时,可执行降级逻辑(如记录日志、跳过本次操作)
Console.WriteLine($"线程 {Thread.CurrentThread.Name}:获取锁超时,跳过本次累加");
}
}
finally
{
// 关键:只有成功获取锁,才需要释放(避免抛 SynchronizationLockException)
if (gotLock)
{
Monitor.Exit(_lockObj); // 释放锁(必须在 finally 中,确保锁一定会释放)
}
}
}
}
}
3.3 ReaderWriterLockSlim(读写锁:读多写少场景优化)
读操作共享,写操作排他,适合 "读多写少" 场景(如缓存查询、配置读取),比 lock 更高效。
cs
using System;
using System.Collections.Generic;
using System.Threading;
class ReaderWriterLockSlimDemo
{
// 读写锁:替代 lock,优化读多写少场景
private static readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
// 共享资源:模拟缓存字典(读多写少)
private static readonly Dictionary<string, string> _cache = new Dictionary<string, string>();
static void Main()
{
// 启动 5 个读线程(模拟高并发读)
for (int i = 0; i < 5; i++)
{
int readerId = i;
new Thread(() => ReadCache(readerId))
{
Name = $"读线程{readerId}",
IsBackground = true
}.Start();
}
// 启动 2 个写线程(模拟低频写)
for (int i = 0; i < 2; i++)
{
int writerId = i;
new Thread(() => WriteCache(writerId))
{
Name = $"写线程{writerId}",
IsBackground = true
}.Start();
}
// 等待所有线程完成
Thread.Sleep(5000);
_rwLock.Dispose(); // 释放读写锁资源
Console.WriteLine("程序执行完毕");
}
/// <summary>
/// 读操作(共享锁:多个线程可同时读)
/// </summary>
/// <param name="readerId">读线程ID</param>
static void ReadCache(int readerId)
{
// 循环模拟多次读操作
for (int i = 0; i < 3; i++)
{
// EnterReadLock:获取读锁(共享锁,多个线程可同时获取)
_rwLock.EnterReadLock();
try
{
// 读临界区:读取共享资源(缓存)
Console.WriteLine($"读线程 {readerId}:缓存数据数 {_cache.Count}(当前读线程可并发)");
Thread.Sleep(500); // 模拟读耗时(如从缓存查询数据)
}
finally
{
// 释放读锁(必须在 finally 中)
_rwLock.ExitReadLock();
}
Thread.Sleep(300); // 模拟两次读之间的间隔
}
}
/// <summary>
/// 写操作(排他锁:仅一个线程可写,写时禁止读)
/// </summary>
/// <param name="writerId">写线程ID</param>
static void WriteCache(int writerId)
{
// 循环模拟多次写操作
for (int i = 0; i < 2; i++)
{
// EnterWriteLock:获取写锁(排他锁,仅一个线程可获取)
// 若已有读锁或写锁,当前线程会阻塞
_rwLock.EnterWriteLock();
try
{
// 写临界区:修改共享资源(缓存)
string key = $"key_{writerId}_{i}";
_cache[key] = $"value_{DateTime.Now:HH:mm:ss.fff}";
Console.WriteLine($"【写线程 {writerId}】:添加缓存 {key}(当前禁止所有读操作)");
Thread.Sleep(1000); // 模拟写耗时(如更新缓存数据)
}
finally
{
// 释放写锁(必须在 finally 中)
_rwLock.ExitWriteLock();
}
Thread.Sleep(800); // 模拟两次写之间的间隔
}
}
}
4. 同步工具对比与选择建议
| 同步工具 | 核心特点 | 适用场景 |
|---|---|---|
lock |
简洁、轻量级、基于 Monitor |
绝大多数单进程内同步场景(优先选择) |
Monitor |
灵活(超时、线程通信)、无额外开销 | 需要精细控制锁的场景 |
SemaphoreSlim |
控制并发数、支持异步 | 限流、连接池、有限资源访问 |
Mutex |
跨进程同步、重量级 | 多个程序共享资源(如文件、硬件设备) |
ReaderWriterLockSlim |
读共享、写排他、高效 | 读多写少场景(缓存、配置、数据库查询) |
四、线程协作:线程通信机制
多线程需协作完成任务时(如生产者 - 消费者问题:生产者生产数据,消费者消费数据),需通过 "线程通信" 实现同步,避免无效等待。
C# 常用线程通信方式:
1. Monitor.Pulse () / Monitor.Wait ()(基于锁)
与 lock / Monitor 配合使用,适用于单进程内线程通信,轻量级。
核心逻辑:
-
消费者线程:条件不满足时(无数据),调用
Monitor.Wait()释放锁,进入等待队列。 -
生产者线程:生产数据后,调用
Monitor.Pulse()唤醒一个等待线程,或PulseAll()唤醒所有等待线程。 -
必须在
lock/Monitor保护的临界区内调用Wait()/Pulse()。
示例:生产者 - 消费者(基于 Monitor)
cs
using System;
using System.Collections.Generic;
using System.Threading;
class ProducerConsumerMonitorDemo
{
// 共享队列:生产者放入数据,消费者取出数据
private static readonly Queue<int> _queue = new Queue<int>();
// 锁对象:用于同步队列操作和线程通信
private static readonly object _lockObj = new object();
// 程序运行标记:控制生产者和消费者线程的循环
private static bool _isRunning = true;
static void Main()
{
Console.WriteLine("生产者-消费者(基于 Monitor)启动...");
// 启动 1 个生产者线程和 1 个消费者线程
Thread producerThread = new Thread(Producer)
{
Name = "生产者线程",
IsBackground = true
};
Thread consumerThread = new Thread(Consumer)
{
Name = "消费者线程",
IsBackground = true
};
producerThread.Start();
consumerThread.Start();
// 让程序运行 5 秒后停止
Thread.Sleep(5000);
_isRunning = false; // 设置标记,停止循环
// 等待生产者和消费者线程完成收尾工作
producerThread.Join();
consumerThread.Join();
Console.WriteLine("程序结束");
}
/// <summary>
/// 生产者线程:生成随机数放入队列
/// </summary>
static void Producer()
{
Random random = new Random(); // 随机数生成器(用于模拟生产数据)
// 循环生产,直到 _isRunning 为 false
while (_isRunning)
{
// 锁定共享资源(队列)
lock (_lockObj)
{
// 限制队列最大容量为 5(避免队列无限增长)
if (_queue.Count < 5)
{
int data = random.Next(100); // 生成 0-99 的随机数(模拟生产的数据)
_queue.Enqueue(data); // 放入队列
Console.WriteLine($"【{Thread.CurrentThread.Name}】:放入数据 {data},队列长度 {_queue.Count}");
// Monitor.Pulse():唤醒一个等待在 _lockObj 上的线程(此处唤醒消费者)
// 注意:Pulse() 不会立即释放锁,需等当前 lock 块执行完毕
Monitor.Pulse(_lockObj);
}
}
// 模拟生产间隔(每 500 毫秒生产一个数据)
Thread.Sleep(500);
}
Console.WriteLine($"{Thread.CurrentThread.Name}:停止生产");
}
/// <summary>
/// 消费者线程:从队列取出数据并消费
/// </summary>
static void Consumer()
{
// 循环消费,直到 _isRunning 为 false 且队列为空
while (_isRunning || _queue.Count > 0)
{
// 锁定共享资源(队列)
lock (_lockObj)
{
// 队列为空时,消费者等待(释放锁,避免占用 CPU 空转)
// 用 while 循环判断:避免虚假唤醒(Wait() 可能被无理由唤醒)
while (_queue.Count == 0 && _isRunning)
{
Console.WriteLine($"【{Thread.CurrentThread.Name}】:队列空,等待生产...");
// Monitor.Wait():释放 _lockObj 的锁,当前线程进入等待队列
// 直到其他线程调用 Pulse()/PulseAll() 唤醒,且重新获取锁后继续执行
Monitor.Wait(_lockObj);
}
// 队列有数据时,消费数据
if (_queue.Count > 0)
{
int data = _queue.Dequeue(); // 从队列取出数据
Console.WriteLine($"【{Thread.CurrentThread.Name}】:消费数据 {data},队列长度 {_queue.Count}");
}
}
// 模拟消费间隔(每 300 毫秒消费一个数据)
Thread.Sleep(300);
}
Console.WriteLine($"{Thread.CurrentThread.Name}:停止消费");
}
}
2. AutoResetEvent / ManualResetEventSlim(事件通知)
基于 "事件信号" 的通信方式,无需依赖锁,适用于线程间简单通知(如 "任务完成" 通知)。
核心区别:
-
AutoResetEvent:调用Set()后,自动重置为 "无信号"(仅唤醒一个等待线程)。 -
ManualResetEventSlim:调用Set()后,保持 "有信号"(唤醒所有等待线程),需手动调用Reset()重置。
示例:任务完成通知(AutoResetEvent)
cs
using System;
using System.Threading;
class EventDemo
{
static void Main()
{
Console.WriteLine("事件通知(AutoResetEvent)演示...");
// AutoResetEvent 构造函数:参数为初始信号状态(false=无信号,true=有信号)
// 初始无信号:工作线程未完成时,主线程等待
AutoResetEvent doneEvent = new AutoResetEvent(false);
// 启动工作线程(执行耗时任务)
Thread workerThread = new Thread(() =>
{
Console.WriteLine("工作线程:开始执行任务(模拟 3 秒耗时)");
Thread.Sleep(3000); // 模拟耗时任务(如下载文件、数据处理)
Console.WriteLine("工作线程:任务完成");
// Set():发送"完成"信号(将事件状态设为有信号)
// AutoResetEvent 特性:Set() 后自动重置为无信号(仅唤醒一个等待线程)
doneEvent.Set();
})
{
Name = "工作线程",
IsBackground = true
};
workerThread.Start();
Console.WriteLine("主线程:等待工作线程完成...");
// WaitOne():阻塞主线程,直到事件收到信号(有信号时返回 true)
// 也可使用 WaitOne(int millisecondsTimeout) 设置超时
doneEvent.WaitOne();
Console.WriteLine("主线程:工作线程已完成,继续执行后续逻辑");
// 释放事件资源(避免内存泄露)
doneEvent.Dispose();
}
}
// 补充:ManualResetEventSlim 示例(手动重置信号)
class ManualResetEventSlimDemo
{
static void Main()
{
// ManualResetEventSlim:轻量级手动重置事件(比 ManualResetEvent 性能好)
// 初始无信号(false)
using (ManualResetEventSlim resetEvent = new ManualResetEventSlim(false))
{
// 启动 3 个工作线程
for (int i = 0; i < 3; i++)
{
int threadId = i;
new Thread(() =>
{
Console.WriteLine($"线程 {threadId}:等待信号...");
resetEvent.Wait(); // 阻塞,直到事件有信号
Console.WriteLine($"线程 {threadId}:收到信号,执行后续操作");
})
{
Name = $"线程{threadId}",
IsBackground = true
}.Start();
}
// 主线程延迟 2 秒后发送信号
Thread.Sleep(2000);
Console.WriteLine("主线程:发送信号(设置事件为有信号)");
resetEvent.Set(); // 设置为有信号(所有等待线程都会被唤醒)
// ManualResetEventSlim 不会自动重置,需手动调用 Reset() 恢复无信号
// resetEvent.Reset(); // 可选:重置为无信号
// 等待所有线程完成
Thread.Sleep(1000);
Console.WriteLine("程序结束");
}
}
}
3. CountdownEvent(倒计时门闩)
等待多个线程完成任务后,主线程再继续执行(如 "等待 10 个下载线程全部完成后,合并文件")。
cs
using System;
using System.Threading;
class CountdownEventDemo
{
static void Main()
{
int threadCount = 5; // 工作线程数量
// CountdownEvent 构造函数:参数为"倒计时次数"(需调用 Signal() 的次数)
CountdownEvent countdown = new CountdownEvent(threadCount);
Console.WriteLine($"主线程:启动 {threadCount} 个工作线程");
// 启动 5 个工作线程
for (int i = 0; i < threadCount; i++)
{
int threadId = i;
new Thread(() =>
{
Console.WriteLine($"工作线程 {threadId}:开始执行任务");
// 模拟任务耗时(线程 ID + 1 秒,让不同线程完成时间不同)
Thread.Sleep(1000 * (threadId + 1));
Console.WriteLine($"工作线程 {threadId}:执行完成");
// Signal():倒计时减 1(每次调用,CurrentCount 减 1)
countdown.Signal();
})
{
Name = $"工作线程{threadId}",
IsBackground = true
}.Start();
}
// Wait():阻塞主线程,直到倒计时为 0(所有工作线程调用 Signal())
countdown.Wait();
Console.WriteLine("所有工作线程完成,主线程继续执行后续逻辑(如合并结果、生成报告)");
// 释放资源(CountdownEvent 实现了 IDisposable)
countdown.Dispose();
}
}
4. Barrier(循环屏障)
多个线程到达 "屏障点" 后,再同时继续执行(如 "多个线程先读取数据,全部读取完成后再统一处理")。
cs
using System;
using System.Threading;
class CountdownEventDemo
{
static void Main()
{
int threadCount = 5; // 工作线程数量
// CountdownEvent 构造函数:参数为"倒计时次数"(需调用 Signal() 的次数)
CountdownEvent countdown = new CountdownEvent(threadCount);
Console.WriteLine($"主线程:启动 {threadCount} 个工作线程");
// 启动 5 个工作线程
for (int i = 0; i < threadCount; i++)
{
int threadId = i;
new Thread(() =>
{
Console.WriteLine($"工作线程 {threadId}:开始执行任务");
// 模拟任务耗时(线程 ID + 1 秒,让不同线程完成时间不同)
Thread.Sleep(1000 * (threadId + 1));
Console.WriteLine($"工作线程 {threadId}:执行完成");
// Signal():倒计时减 1(每次调用,CurrentCount 减 1)
countdown.Signal();
})
{
Name = $"工作线程{threadId}",
IsBackground = true
}.Start();
}
// Wait():阻塞主线程,直到倒计时为 0(所有工作线程调用 Signal())
countdown.Wait();
Console.WriteLine("所有工作线程完成,主线程继续执行后续逻辑(如合并结果、生成报告)");
// 释放资源(CountdownEvent 实现了 IDisposable)
countdown.Dispose();
}
}
五、线程池:ThreadPool(复用线程,提升性能)
频繁创建 / 销毁线程会产生大量开销(操作系统分配资源、回收资源),ThreadPool 是 .NET 内置的线程池,通过 "复用线程" 减少开销,适用于短期任务。
1. 核心特性
-
线程池中的线程默认是后台线程。
-
线程池会自动管理线程数量(核心线程数 + 临时线程数),避免线程过多导致的 CPU 切换开销。
-
不支持设置线程优先级、名称(如需这些特性,需手动创建
Thread)。
2. 线程池的使用(核心方法)
2.1 提交任务:QueueUserWorkItem
cs
using System;
using System.Threading;
class ThreadPoolDemo
{
static void Main()
{
Console.WriteLine("线程池示例启动...");
// 1. 提交无参数任务到线程池
// QueueUserWorkItem:将任务加入线程池队列,线程池空闲时执行
// 参数:无参数的回调方法(WaitCallback 委托,签名为 void MethodName(object state))
ThreadPool.QueueUserWorkItem(DoWork);
// 2. 提交带参数任务到线程池
// 第二个参数:传递给回调方法的参数(object 类型,需拆箱)
ThreadPool.QueueUserWorkItem(DoWorkWithParam, "线程池传递的参数");
Console.WriteLine("主线程:任务已提交到线程池,等待任务完成...");
// 线程池线程是后台线程,主线程退出后任务会终止,因此需要让主线程等待
Thread.Sleep(3000);
Console.WriteLine("主线程执行完毕");
}
/// <summary>
/// 线程池无参数任务回调(WaitCallback 委托)
/// </summary>
/// <param name="state">线程池传递的参数(无参数时为 null)</param>
static void DoWork(object state)
{
//Thread.CurrentThread.ManagedThreadId:获取线程池线程的 ID(用于区分)
Console.WriteLine($"线程池线程(ID:{Thread.CurrentThread.ManagedThreadId}):无参数任务执行");
Thread.Sleep(1000); // 模拟任务耗时(短期任务,适合线程池)
Console.WriteLine($"线程池线程(ID:{Thread.CurrentThread.ManagedThreadId}):无参数任务完成");
}
/// <summary>
/// 线程池带参数任务回调
/// </summary>
/// <param name="state">线程池传递的参数(需手动拆箱)</param>
static void DoWorkWithParam(object state)
{
// 拆箱:将 object 类型转为 string
string param = state as string;
Console.WriteLine($"线程池线程(ID:{Thread.CurrentThread.ManagedThreadId}):带参数任务执行,参数:{param}");
Thread.Sleep(1000);
Console.WriteLine($"线程池线程(ID:{Thread.CurrentThread.ManagedThreadId}):带参数任务完成");
}
}
2.2 线程池配置(核心线程数、最大线程数)
可通过 ThreadPool.SetMinThreads / ThreadPool.SetMaxThreads 调整线程池参数(需在提交任务前设置):
cs
using System;
using System.Threading;
class ThreadPoolConfigDemo
{
static void Main()
{
int workerThreads, completionPortThreads;
// GetMinThreads:获取当前线程池的最小线程数配置
// workerThreads:工作线程数(处理 CPU 密集型任务)
// completionPortThreads:IO 完成端口线程数(处理 IO 密集型任务,如异步 IO 回调)
ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
Console.WriteLine($"默认最小工作线程数:{workerThreads}");
Console.WriteLine($"默认最小 IO 完成端口线程数:{completionPortThreads}");
// GetMaxThreads:获取当前线程池的最大线程数配置
ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
Console.WriteLine($"默认最大工作线程数:{workerThreads}");
Console.WriteLine($"默认最大 IO 完成端口线程数:{completionPortThreads}");
// SetMinThreads:设置最小工作线程数(IO 密集型任务可适当增大)
// 最小线程数:线程池会至少维持该数量的空闲线程,避免任务等待
bool setSuccess = ThreadPool.SetMinThreads(10, completionPortThreads);
Console.WriteLine($"设置最小工作线程数为 10:{setSuccess}(true=设置成功)");
// 提交任务到线程池(验证配置)
for (int i = 0; i < 15; i++)
{
int taskId = i;
ThreadPool.QueueUserWorkItem((state) =>
{
Console.WriteLine($"任务 {state}:线程池线程 ID:{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(500);
}, taskId);
}
// 等待任务完成
Thread.Sleep(2000);
}
}
3. 线程池的适用场景与注意事项
-
适用场景:短期、轻量级任务(如网络请求回调、数据处理)。
-
不适用场景:
-
长时间运行的任务(会占用线程池线程,导致其他任务排队)。
-
需要设置线程优先级、名称的场景。
-
-
注意事项:
-
线程池任务是后台线程,主线程退出后任务会终止,需确保主线程等待。
-
避免在任务中执行长时间阻塞操作(如
Thread.Sleep(Timeout.Infinite)),会导致线程池线程耗尽。
-
六、高级并发:Task 与 async/await(.NET 推荐方案)
Task 是 .NET Framework 4.0+ 引入的任务抽象 ,位于 System.Threading.Tasks 命名空间,是 Thread 和 ThreadPool 的高级封装,支持异步等待、结果返回、任务取消、进度报告等,是 .NET 并发编程的首选方案。
1. Task 与 Thread/ThreadPool 的核心区别
| 特性 | Thread(传统线程) | ThreadPool(线程池) | Task(任务) |
|---|---|---|---|
| 资源开销 | 高(创建 / 销毁成本高) | 中(复用线程) | 低(基于线程池,支持异步) |
| 返回结果 | 不支持(需手动处理) | 不支持(需手动处理) | 原生支持(Task<TResult>) |
| 异步等待 | Join()(阻塞) |
无原生支持(需手动等待) | await/Wait()(灵活) |
| 取消机制 | 无原生支持(Abort 过时) |
无原生支持 | 原生支持 CancellationToken |
| 异常处理 | 线程内捕获(否则崩溃) | 线程内捕获(否则崩溃) | 统一捕获(await/Exception) |
| 易用性 | 低(需手动管理) | 中(仅支持简单任务) | 高(async/await 语法糖) |
2. Task 的创建与启动(3 种核心方式)
方式 1:Task.Run(最常用,默认入线程池)
适用于后台任务、CPU/IO 密集型任务,创建即调度(无需手动 Start())。
cs
using System;
using System.Threading;
using System.Threading.Tasks;
class TaskRunDemo
{
// C# 7.1+ 支持 async Main 方法(作为异步程序入口)
static async Task Main()
{
Console.WriteLine($"主线程(ID:{Thread.CurrentThread.ManagedThreadId}):启动任务...");
// 1. 创建无返回值 Task(Task)
// Task.Run(Action):将任务提交到线程池,返回 Task 对象(代表任务本身)
Task task1 = Task.Run(() =>
{
Console.WriteLine($"Task1(线程 ID:{Thread.CurrentThread.ManagedThreadId}):无返回值任务执行");
// Task.Delay(1000):异步休眠 1 秒(不阻塞线程,优于 Thread.Sleep)
Task.Delay(1000).Wait(); // 此处用 Wait() 是同步等待(仅示例,实际推荐用 await)
});
// 2. 创建有返回值 Task(Task<TResult>)
// Task.Run(Func<TResult>):任务执行完毕后返回 TResult 类型结果
Task<int> task2 = Task.Run(() =>
{
Console.WriteLine($"Task2(线程 ID:{Thread.CurrentThread.ManagedThreadId}):有返回值任务执行");
Task.Delay(1000).Wait();
return 10 + 20; // 任务结果(30)
});
// 异步等待任务完成(await 不会阻塞主线程,会释放主线程执行其他逻辑)
await task1; // 等待 task1 完成(无返回值)
int result = await task2; // 等待 task2 完成并获取结果
Console.WriteLine($"主线程(ID:{Thread.CurrentThread.ManagedThreadId}):Task2 结果:{result}");
Console.WriteLine("所有任务执行完毕");
}
}
方式 2:new Task () + Start ()(手动控制启动)
适用于需要延迟启动的场景(创建后不立即执行)。
cs
using System;
using System.Threading;
using System.Threading.Tasks;
class TaskNewStartDemo
{
static async Task Main()
{
Console.WriteLine($"主线程(ID:{Thread.CurrentThread.ManagedThreadId}):创建任务...");
// 1. 创建无返回值 Task(未启动)
Task task3 = new Task(() =>
{
Console.WriteLine($"Task3(线程 ID:{Thread.CurrentThread.ManagedThreadId}):手动启动的无返回值任务");
Task.Delay(1000).Wait();
});
// 2. 创建有返回值 Task(Task<TResult>,未启动)
Task<string> task4 = new Task<string>(() =>
{
Console.WriteLine($"Task4(线程 ID:{Thread.CurrentThread.ManagedThreadId}):手动启动的有返回值任务");
Task.Delay(500).Wait();
return "Task4 执行完成后的返回值";
});
// 延迟 1 秒后启动任务(模拟"按需启动"场景)
Console.WriteLine("主线程:延迟 1 秒后启动任务...");
Task.Delay(1000).Wait();
task3.Start(); // 必须调用 Start() 方法,任务才会进入调度队列
task4.Start();
// 异步等待任务完成
await task3;
string strResult = await task4;
Console.WriteLine($"主线程:Task4 返回值:{strResult}");
}
}
方式 3:Task.Factory.StartNew(灵活配置任务选项)
支持通过 TaskCreationOptions 配置任务行为(如长时间运行、独立线程)。
cs
using System;
using System.Threading;
using System.Threading.Tasks;
class TaskFactoryDemo
{
static async Task Main()
{
Console.WriteLine($"主线程(ID:{Thread.CurrentThread.ManagedThreadId}):启动配置化任务...");
// Task.Factory.StartNew():灵活配置任务选项
// 参数 1:任务执行逻辑;参数 2:任务创建选项(TaskCreationOptions)
Task longTask = Task.Factory.StartNew(() =>
{
Console.WriteLine($"长时间任务(线程 ID:{Thread.CurrentThread.ManagedThreadId}):开始执行(独立线程,不占用线程池)");
// 模拟长时间运行任务(如监控程序、持续数据采集)
Task.Delay(5000).Wait();
Console.WriteLine($"长时间任务:执行完成");
},
// TaskCreationOptions.LongRunning:告诉任务调度器,这是长时间运行的任务
// 调度器会创建独立的操作系统线程(而非使用线程池线程),避免占用线程池资源
TaskCreationOptions.LongRunning);
// 等待长时间任务完成
await longTask;
Console.WriteLine("主线程:长时间任务执行完毕");
}
}
3. Task 的核心高级特性
3.1 异步等待:async/await 语法(.NET 核心)
async/await 是 .NET 异步编程的语法糖,可让异步代码像同步代码一样简洁,避免回调地狱,且不阻塞当前线程(如 UI 线程)。
核心规则:
-
方法标记为
async,返回值只能是void(仅用于事件处理)、Task(无返回值)、Task<TResult>(有返回值)。 -
await只能在async方法中使用,用于等待Task完成,await后的代码会在任务完成后执行。 -
UI 程序中,
await后的代码会自动回到 UI 线程(无需手动切换)。
示例:异步下载文件(IO 密集型任务)
cs
using System;
using System.Net;
using System.Threading.Tasks;
class AsyncAwaitDemo
{
/// <summary>
/// 异步下载文件(IO 密集型任务,推荐用 async/await)
/// </summary>
/// <param name="url">文件下载地址</param>
/// <param name="savePath">本地保存路径</param>
/// <returns>无返回值 Task(异步方法标记为 async Task)</returns>
static async Task DownloadFileAsync(string url, string savePath)
{
Console.WriteLine($"下载线程(ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}):开始下载文件...");
// using 语句:自动释放 WebClient 资源(实现 IDisposable 接口)
using (WebClient client = new WebClient())
{
// DownloadFileTaskAsync:WebClient 的异步下载方法(返回 Task)
// await:等待异步任务完成,期间不阻塞当前线程(如 UI 线程可继续响应操作)
// 注意:await 只能在 async 标记的方法中使用
await client.DownloadFileTaskAsync(new Uri(url), savePath);
}
Console.WriteLine($"下载线程(ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}):文件下载完成");
}
/// <summary>
/// 异步计算(CPU 密集型任务)
/// </summary>
/// <param name="a">第一个参数</param>
/// <param name="b">第二个参数</param>
/// <returns>有返回值 Task<int>(异步方法标记为 async Task<TResult>)</returns>
static async Task<int> CalculateAsync(int a, int b)
{
Console.WriteLine($"计算线程(ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}):开始异步计算...");
// 用 Task.Run 包装 CPU 密集型任务(避免阻塞调用线程)
// await 等待计算完成,返回结果
int result = await Task.Run(() =>
{
Task.Delay(1000).Wait(); // 模拟计算耗时
return a + b; // 计算结果
});
Console.WriteLine($"计算线程(ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}):计算完成");
return result;
}
// 异步 Main 方法(程序入口)
static async Task Main()
{
Console.WriteLine($"主线程(ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}):启动异步任务...");
// 1. 调用无返回值异步方法(下载文件)
// 注意:await 会等待方法执行完成,期间主线程不阻塞(可执行其他逻辑)
await DownloadFileAsync("https://example.com/test.txt", "test.txt");
// 2. 调用有返回值异步方法(计算)
int sum = await CalculateAsync(15, 25);
Console.WriteLine($"主线程:计算结果:{sum}(预期 40)");
Console.WriteLine($"主线程(ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}):所有异步任务完成");
}
}
3.2 任务取消:CancellationToken
通过 CancellationTokenSource(CTS)和 CancellationToken(CT)实现任务的 "优雅取消"(非强制终止),避免资源泄露。
cs
using System;
using System.Threading;
using System.Threading.Tasks;
class TaskCancellationDemo
{
static async Task Main()
{
Console.WriteLine("任务取消示例(3 秒后自动取消)...");
// 1. 创建取消令牌源(CancellationTokenSource)
// 构造函数参数:超时时间(3000 毫秒=3 秒),超时后自动触发取消
using (CancellationTokenSource cts = new CancellationTokenSource(3000))
{
// 获取取消令牌(CancellationToken):传递给任务,用于接收取消信号
CancellationToken ct = cts.Token;
// 2. 启动可取消任务
Task task = Task.Run(() =>
{
// 循环执行任务(模拟持续运行的任务)
for (int i = 0; i < 10; i++)
{
// 检查是否收到取消请求:若已取消,抛出 OperationCanceledException
// 必须在任务中主动检查,否则任务不会响应取消
ct.ThrowIfCancellationRequested();
Console.WriteLine($"任务执行中:第 {i+1} 次循环");
Task.Delay(500).Wait(); // 每次循环耗时 500 毫秒
}
}, ct); // 将取消令牌传入 Task(用于注册取消回调)
// 可选:手动取消(如用户点击取消按钮,此处注释掉,用自动超时取消)
// Task.Run(() =>
// {
// Console.WriteLine("按任意键手动取消任务...");
// Console.ReadKey();
// cts.Cancel(); // 手动触发取消
// });
// 3. 捕获取消异常(OperationCanceledException)
try
{
await task; // 等待任务完成
Console.WriteLine("任务正常完成(未被取消)");
}
catch (OperationCanceledException)
{
// 捕获取消异常(正常流程,非错误)
Console.WriteLine("任务已被成功取消(超时或手动取消)");
}
}
// 注意:using 语句会自动释放 CancellationTokenSource 资源
}
}
3.3 进度报告:IProgress<T>
用于任务执行过程中向调用方(如 UI 线程)报告进度,无需手动处理线程切换。
cs
using System;
using System.Threading.Tasks;
class TaskProgressDemo
{
static async Task Main()
{
Console.WriteLine("带进度报告的任务示例...");
// 1. 创建进度报告器(IProgress<T>)
// T 是进度数据类型(此处为 int,代表百分比;也可自定义类)
// 构造函数参数:进度回调函数(任务报告进度时执行)
IProgress<int> progress = new Progress<int>(percent =>
{
// 关键特性:回调函数会自动在"创建 Progress 对象的线程"(此处是主线程)执行
// 无需手动切换线程(如 UI 线程无需 Invoke)
Console.WriteLine($"主线程:当前任务进度:{percent}%");
});
// 2. 启动带进度报告的任务
await DoTaskWithProgress(progress);
Console.WriteLine("任务执行完成");
}
/// <summary>
/// 带进度报告的异步任务
/// </summary>
/// <param name="progress">进度报告器(由调用方传入)</param>
/// <returns>Task(异步任务)</returns>
static async Task DoTaskWithProgress(IProgress<int> progress)
{
// 模拟任务分 11 步执行(0% ~ 100%)
for (int i = 0; i <= 100; i += 10)
{
await Task.Delay(300); // 模拟每步耗时 300 毫秒(如下载、处理数据)
// 报告进度:调用 Report() 方法,传入当前进度
// 进度报告器会自动将进度回调到创建线程
progress.Report(i);
}
}
}
3.4 批量任务处理:WhenAll / WhenAny
-
Task.WhenAll:等待所有任务完成,返回所有任务的结果(适用于 "所有任务都完成后再继续")。
-
Task.WhenAny:等待任意一个任务完成,返回最先完成的任务(适用于 "取最快完成的任务结果")。
cs
using System;
using System.Threading.Tasks;
class TaskBatchDemo
{
static async Task Main()
{
Console.WriteLine("批量任务处理示例(WhenAll + WhenAny)...");
// -------------------------- 示例 1:Task.WhenAll(等待所有任务)--------------------------
Console.WriteLine("\n=== 启动 WhenAll 任务(3 个任务并行执行,等待全部完成)===");
// 创建 3 个有返回值的任务(模拟不同耗时)
Task<int> taskA = Task.Run(() => { Task.Delay(1000).Wait(); return 1; }); // 1 秒
Task<int> taskB = Task.Run(() => { Task.Delay(2000).Wait(); return 2; }); // 2 秒
Task<int> taskC = Task.Run(() => { Task.Delay(1500).Wait(); return 3; }); // 1.5 秒
// WhenAll:等待所有任务完成,返回所有结果的数组(顺序与传入任务一致)
int[] allResults = await Task.WhenAll(taskA, taskB, taskC);
Console.WriteLine($"WhenAll 所有任务完成,结果:{string.Join(", ", allResults)}"); // 输出 1, 2, 3
// -------------------------- 示例 2:Task.WhenAny(等待任意一个任务)--------------------------
Console.WriteLine("\n=== 启动 WhenAny 任务(2 个任务并行,取最快完成)===");
Task<string> fastTask = Task.Run(() => { Task.Delay(500).Wait(); return "快速任务(500 毫秒)"; });
Task<string> slowTask = Task.Run(() => { Task.Delay(1500).Wait(); return "慢速任务(1500 毫秒)"; });
// WhenAny:等待任意一个任务完成,返回最先完成的任务对象
Task<string> completedTask = await Task.WhenAny(fastTask, slowTask);
// 等待完成的任务,获取其结果
string result = await completedTask;
Console.WriteLine($"WhenAny 先完成的任务:{result}");
// 注意:未完成的任务会继续执行,若需取消,可结合 CancellationToken
// 此处简单等待未完成任务(避免程序提前退出)
await Task.WhenAll(fastTask, slowTask);
Console.WriteLine("所有任务均已完成");
}
}
3.5 任务链:ContinueWith
任务完成后自动执行后续操作(替代回调嵌套),但推荐优先使用 async/await(更简洁)。
cs
using System;
using System.Threading.Tasks;
class TaskContinueWithDemo
{
static async Task Main()
{
Console.WriteLine("任务链(ContinueWith)示例...");
// 任务链:任务 1 完成后执行任务 2,任务 2 完成后执行任务 3
Task.Run(() =>
{
Console.WriteLine("任务 1:执行(模拟 1 秒耗时)");
Task.Delay(1000).Wait();
})
// ContinueWith:当前任务完成后,执行下一个任务(回调函数)
.ContinueWith((previousTask) =>
{
// previousTask:上一个任务的实例(可获取上一个任务的状态、异常等)
Console.WriteLine($"任务 2:任务 1 已完成(状态:{previousTask.Status}),执行任务 2(模拟 500 毫秒耗时)");
Task.Delay(500).Wait();
})
.ContinueWith((previousTask) =>
{
Console.WriteLine($"任务 3:任务 2 已完成(状态:{previousTask.Status}),执行任务 3");
});
// 等待任务链完成(避免主线程提前退出)
await Task.Delay(2000);
Console.WriteLine("任务链执行完毕");
}
}
4. Task 的异常处理
Task 的异常不会直接崩溃程序,需通过以下方式捕获(推荐 await + try-catch):
方式 1:await + try-catch(最推荐)
cs
using System;
using System.Threading.Tasks;
class TaskExceptionDemo1
{
static async Task Main()
{
Console.WriteLine("Task 异常处理(await + try-catch)示例...");
try
{
// 启动一个会抛出异常的 Task
await Task.Run(() =>
{
Console.WriteLine("任务执行中,准备抛出异常...");
// 模拟业务异常(参数错误)
throw new ArgumentException("任务执行时发生参数错误:传入的 ID 无效");
});
}
catch (ArgumentException ex)
{
// 直接捕获原始异常(无需处理 AggregateException)
Console.WriteLine($"捕获到异常:{ex.Message}");
Console.WriteLine($"异常类型:{ex.GetType().Name}");
}
Console.WriteLine("程序继续执行(未因异常崩溃)");
}
}
方式 2:Task.Exception(非 await 场景)
非 await 场景下,Task 异常会被包装为 AggregateException,需通过 Flatten() 提取原始异常:
cs
using System;
using System.Threading.Tasks;
class TaskExceptionDemo2
{
static void Main()
{
Console.WriteLine("Task 异常处理(Task.Exception)示例...");
// 启动一个会抛出异常的 Task(未使用 await)
Task faultTask = Task.Run(() =>
{
Console.WriteLine("任务执行中,准备抛出异常...");
throw new NullReferenceException("任务执行时发生空引用异常:未初始化对象");
});
try
{
// Wait():阻塞等待任务完成,触发异常
faultTask.Wait();
// 若任务有返回值,用 Result 属性也会触发异常:int result = faultTask.Result;
}
catch (AggregateException ex)
{
// 非 await 场景下,Task 异常会被包装为 AggregateException
Console.WriteLine($"捕获到 AggregateException:{ex.Message}");
// Flatten():处理嵌套的 AggregateException(如多个任务异常)
// InnerException:获取原始异常(单个任务时)
Exception innerEx = ex.Flatten().InnerException;
Console.WriteLine($"原始异常类型:{innerEx.GetType().Name}");
Console.WriteLine($"原始异常信息:{innerEx.Message}");
}
Console.WriteLine("程序继续执行");
}
}
5. ValueTask<TResult>(.NET Core 2.1+)
ValueTask<TResult> 是值类型(struct),默认不分配堆内存,适合 "异步操作大概率快速完成" 的场景(如缓存命中、本地文件快速读取),性能优于 Task<TResult>。
cs
using System;
using System.Threading.Tasks;
class ValueTaskDemo
{
// .NET Core 2.1+ 支持 ValueTask<TResult>
static async ValueTask<int> FastOrSlowTask(bool isFast)
{
// 场景 1:快速完成(如缓存命中、本地数据读取)
if (isFast)
{
Console.WriteLine("任务快速完成(无堆内存分配)");
return 100; // 直接返回结果,无需分配 Task 对象(值类型优势)
}
// 场景 2:耗时操作(如网络请求、数据库查询)
// 自动装箱为 Task<int>(仅耗时场景才分配堆内存)
Console.WriteLine("任务耗时执行(分配堆内存)");
return await Task.Run(() => 200);
}
static async Task Main()
{
Console.WriteLine("ValueTask<TResult> 示例...");
// 测试快速完成场景
int result1 = await FastOrSlowTask(true);
Console.WriteLine($"快速任务结果:{result1}(预期 100)");
// 测试耗时场景
int result2 = await FastOrSlowTask(false);
Console.WriteLine($"耗时任务结果:{result2}(预期 200)");
}
}
七、线程安全集合(避免手动同步)
System.Collections.Concurrent 命名空间提供了线程安全的集合类,内部已实现同步机制,无需手动加锁,适合多线程读写场景。
常用线程安全集合
| 集合类 | 功能说明 | 适用场景 |
|---|---|---|
ConcurrentDictionary<TKey, TValue> |
线程安全的字典(键值对) | 多线程读写键值对(如缓存) |
ConcurrentQueue<T> |
线程安全的队列(FIFO) | 生产者 - 消费者场景(如任务队列) |
ConcurrentStack<T> |
线程安全的栈(LIFO) | 多线程栈操作(如调用栈、撤销操作) |
ConcurrentBag<T> |
线程安全的无序集合 | 无需排序的多线程数据存储 |
BlockingCollection<T> |
支持阻塞的线程安全集合(基于队列 / 栈) | 生产者 - 消费者场景(自动阻塞空队列读取) |
示例:BlockingCollection(生产者 - 消费者简化版)
cs
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
class BlockingCollectionDemo
{
// 1. 初始化阻塞队列:
// - 泛型参数<int>:指定队列存储int类型数据
// - 构造参数(5):设置队列最大容量为5,超过容量时生产者会自动阻塞
// - BlockingCollection内部已实现线程安全,无需手动加锁
private static readonly BlockingCollection<int> _blockingQueue = new BlockingCollection<int>(5);
// 2. 程序运行控制标记:用于通知生产者停止生产
private static bool _isRunning = true;
// 异步Main方法:C# 7.1+支持,作为异步程序入口
static async Task Main()
{
Console.WriteLine("=== 生产者-消费者(基于BlockingCollection)启动 ===");
// 3. 启动生产者和消费者任务(用Task.Run简化线程创建,默认使用线程池)
// 生产者任务:负责生成数据并放入队列
Task producerTask = Task.Run(Producer);
// 消费者任务:负责从队列取出数据并消费
Task consumerTask = Task.Run(Consumer);
// 4. 让程序运行5秒后自动停止(模拟业务运行时长)
await Task.Delay(5000); // 异步休眠,不阻塞主线程(优于Thread.Sleep)
// 5. 停止生产流程:
_isRunning = false; // 通知生产者退出循环
// CompleteAdding():标记队列"不再接受新数据",消费者遍历到队尾时会退出
// 若不调用此方法,消费者的GetConsumingEnumerable()会一直阻塞等待新数据
_blockingQueue.CompleteAdding();
// 6. 等待生产者和消费者任务完成收尾工作(避免后台任务被强制终止)
await Task.WhenAll(producerTask, consumerTask);
Console.WriteLine("=== 程序结束 ===");
}
/// <summary>
/// 生产者方法:生成随机数并放入阻塞队列
/// </summary>
static void Producer()
{
// 初始化随机数生成器(避免多线程共享时产生重复随机数)
Random random = new Random();
// 循环生产:只要程序运行标记为true,就持续生成数据
while (_isRunning)
{
// 生成0-99的随机数(模拟业务数据,如订单ID、传感器数据)
int data = random.Next(100);
// 向队列添加数据:
// - 若队列未满(<5):直接添加,立即返回
// - 若队列已满(=5):自动阻塞当前线程,直到队列有空闲位置
// - 无需手动处理锁和等待逻辑,集合内部已实现
_blockingQueue.Add(data);
// 输出生产日志:显示当前生产的数据和队列长度
Console.WriteLine($"【生产者】:添加数据 {data},当前队列长度:{_blockingQueue.Count}");
// 模拟生产间隔:每500毫秒生产一个数据(避免生产过快导致队列一直满)
Task.Delay(500).Wait(); // 同步休眠(生产者是后台任务,阻塞不影响主线程)
}
Console.WriteLine("【生产者】:停止生产");
}
/// <summary>
/// 消费者方法:从阻塞队列取出数据并消费
/// </summary>
static void Consumer()
{
// GetConsumingEnumerable():获取队列的消费枚举器,核心特性:
// 1. 线程安全:多消费者同时调用也不会出现数据竞争
// 2. 自动阻塞:队列空时,当前线程会阻塞,直到有新数据或CompleteAdding()被调用
// 3. 自动退出:当队列空且已调用CompleteAdding(),枚举器会终止,循环退出
foreach (int data in _blockingQueue.GetConsumingEnumerable())
{
// 模拟消费逻辑:如数据处理、存储到数据库等
Console.WriteLine($"【消费者】:消费数据 {data},当前队列长度:{_blockingQueue.Count}");
// 模拟消费间隔:每300毫秒消费一个数据(消费速度比生产快,避免队列堆积)
Task.Delay(300).Wait();
}
Console.WriteLine("【消费者】:停止消费(队列已无数据且停止添加)");
}
}
八、高级主题:死锁、调优与最佳实践
1. 死锁的成因与避免
死锁是多线程同步时的致命问题:多个线程互相等待对方持有的资源,导致所有线程永久阻塞,程序无法继续执行。
1.1 死锁的 4 个必要条件(缺一不可)
-
互斥条件 :资源只能被一个线程占用(如
lock锁定的对象,同一时间仅一个线程能持有)。 -
持有并等待条件:线程持有一个资源后,不释放,同时等待另一个资源。
-
不可剥夺条件 :资源不能被强制从持有线程手中夺走(如
lock的锁不能被其他线程强制释放)。 -
循环等待条件:多个线程形成闭环等待(如线程 A 等线程 B 的资源,线程 B 等线程 A 的资源)。
1.2 死锁示例(循环等待)
cs
using System;
using System.Threading;
class DeadlockDemo
{
// 定义两个独立的锁对象(模拟两个不同的资源,如文件A、数据库连接B)
private static readonly object _lock1 = new object(); // 资源1的锁
private static readonly object _lock2 = new object(); // 资源2的锁
static void Main()
{
Console.WriteLine("=== 死锁示例启动(观察控制台输出,程序会永久阻塞)===");
// 创建两个线程,分别执行不同的任务(需要获取两个锁)
Thread thread1 = new Thread(DoWork1); // 线程1:先拿锁1,再拿锁2
Thread thread2 = new Thread(DoWork2); // 线程2:先拿锁2,再拿锁1
thread1.Start(); // 启动线程1
thread2.Start(); // 启动线程2
// 等待两个线程完成(实际会永久阻塞,因为死锁)
thread1.Join();
thread2.Join();
Console.WriteLine("程序结束(永远不会执行到这里)");
}
/// <summary>
/// 线程1的任务:先获取_lock1,再获取_lock2
/// </summary>
static void DoWork1()
{
// 1. 线程1获取资源1的锁(满足"互斥条件")
lock (_lock1)
{
Console.WriteLine("线程1:已持有 资源1(_lock1),正在等待 资源2(_lock2)");
// 模拟业务处理耗时:让线程1有足够时间持有_lock1,让线程2先获取_lock2
// 此处休眠是为了触发死锁(实际开发中,耗时操作可能是IO、计算等)
Task.Delay(100).Wait();
// 2. 线程1尝试获取资源2的锁,但此时资源2已被线程2持有(满足"持有并等待")
lock (_lock2)
{
// 以下代码永远不会执行(死锁已发生)
Console.WriteLine("线程1:已持有 资源1 和 资源2,执行任务...");
}
}
}
/// <summary>
/// 线程2的任务:先获取_lock2,再获取_lock1(与线程1的锁顺序相反)
/// </summary>
static void DoWork2()
{
// 1. 线程2获取资源2的锁(满足"互斥条件")
lock (_lock2)
{
Console.WriteLine("线程2:已持有 资源2(_lock2),正在等待 资源1(_lock1)");
// 模拟业务处理耗时:让线程2有足够时间持有_lock2,让线程1先获取_lock1
Task.Delay(100).Wait();
// 2. 线程2尝试获取资源1的锁,但此时资源1已被线程1持有(满足"持有并等待")
lock (_lock1)
{
// 以下代码永远不会执行(死锁已发生)
Console.WriteLine("线程2:已持有 资源2 和 资源1,执行任务...");
}
}
}
}
1.3 死锁避免的核心方案(针对 4 个必要条件)
| 解决方案 | 作用(破坏哪个条件) | 实现方式 |
|---|---|---|
| 统一锁获取顺序 | 破坏 "循环等待"(最常用) | 所有线程按固定顺序获取锁(如先拿_lock1,再拿_lock2,线程 2 修改为与线程 1 一致) |
| 超时获取锁 | 破坏 "持有并等待" | 使用Monitor.TryEnter(lockObj, 100)或SemaphoreSlim.Wait(100),超时则释放已持锁 |
| 一次性获取所有资源 | 破坏 "持有并等待" | 设计接口,一次性申请所有需要的资源,申请失败则不持有任何资源 |
| 允许资源剥夺 | 破坏 "不可剥夺" | 使用CancellationToken,超时或取消时释放已持有的锁 |
1.4 修复上述死锁示例(统一锁顺序)
cs
static void DoWork2()
{
// 修复:统一锁顺序,先拿_lock1,再拿_lock2(与线程1一致)
lock (_lock1)
{
Console.WriteLine("线程2:已持有 资源1(_lock1),正在等待 资源2(_lock2)");
Task.Delay(100).Wait();
lock (_lock2)
{
Console.WriteLine("线程2:已持有 资源1 和 资源2,执行任务...");
}
}
}
修复后,线程 1 和线程 2 按相同顺序获取锁,不会形成循环等待,死锁问题解决。
2. 并发编程调优技巧
2.1 线程数调优
-
CPU 密集型任务 (如计算、排序):线程数 ≈ CPU 核心数(
Environment.ProcessorCount),避免过多线程导致 CPU 切换开销。 -
IO 密集型任务(如网络请求、数据库操作):线程数可大于 CPU 核心数(如核心数 ×2~5),因为线程大部分时间在等待 IO 完成。
cs// 获取CPU核心数 int cpuCount = Environment.ProcessorCount; // IO密集型任务:设置线程池最小工作线程数为CPU核心数×3 ThreadPool.SetMinThreads(cpuCount * 3, ThreadPool.GetMinThreads(out _, out int ioThreads));
2.2 减少锁竞争
-
缩小临界区:只在操作共享资源的代码上加锁,避免锁包裹无关耗时操作。
-
锁粒度拆分:将大锁拆分为多个小锁(如
ConcurrentDictionary的分段锁)。 -
用无锁结构:优先使用
System.Collections.Concurrent集合、Interlocked原子操作(如Interlocked.Increment(ref count))。
2.3 避免阻塞线程池
-
线程池线程是宝贵资源,避免在任务中执行长时间阻塞操作(如
Thread.Sleep(Timeout.Infinite)、未设置超时的WaitOne())。 -
长时间任务:使用TaskCreationOptions.LongRunning标记,让任务调度器创建独立线程(而非线程池线程)。
cs// 长时间运行的任务(如监控程序) Task longTask = Task.Factory.StartNew(() => { while (true) { /* 持续运行 */ } }, TaskCreationOptions.LongRunning);
3. 并发编程最佳实践
-
优先使用
Task+async/await:相比Thread和ThreadPool,更简洁、支持取消 / 进度 / 异常处理,是.NET 推荐的异步并发方案。 -
避免使用
Thread.Abort():强制终止线程会导致资源泄露(如锁未释放、文件句柄未关闭),用CancellationToken实现优雅取消。 -
线程安全集合替代手动同步 :多线程读写集合时,优先使用
ConcurrentDictionary、BlockingCollection等,避免手动加锁出错。 -
锁对象使用私有只读引用类型 :避免使用
this、string(字符串池复用)作为锁对象,防止外部线程误操作锁。 -
异步方法命名规范 :异步方法后缀加
Async(如DownloadFileAsync),无返回值时返回Task(而非void,除事件处理程序)。 -
捕获并处理任务异常 :避免任务异常未捕获导致程序崩溃,用
await + try-catch或task.ContinueWith处理异常。
九、总结
本文覆盖了 C# 线程与并发编程的所有核心知识点 ,从基础的 Thread 类、同步机制、线程通信,到高级的 Task、async/await、线程池、线程安全集合,再到死锁避免和调优实践,形成了完整的知识体系。
核心学习路径:
-
理解线程与进程的关系、线程生命周期(基础)。
-
掌握
Thread类的基本使用(创建、启动、等待)。 -
学会同步机制(
lock、SemaphoreSlim等),解决资源竞争。 -
掌握
Task+async/await(重点),应对绝大多数并发场景。 -
了解线程池、线程安全集合,优化程序性能。
-
规避死锁、合理调优,写出高效稳定的并发程序。
C# 线程与并发核心 API 速查表
(按场景分类,方便开发快速查阅,搭配之前的详细教程使用)
一、基础线程(Thread 类)
| 类别 | API 语法 / 方法 | 用途说明 |
|---|---|---|
| 构造函数 | new Thread(ThreadStart) |
创建无参数线程(ThreadStart 委托:void Method()) |
new Thread(ParameterizedThreadStart) |
创建带参数线程(委托:void Method(object)) |
|
| 核心方法 | Start() |
启动线程(进入就绪状态,仅调用一次) |
Join() / Join(int ms) |
阻塞当前线程,等待目标线程完成(支持超时) | |
Sleep(int ms) |
线程休眠(释放 CPU,不释放锁,单位:毫秒) | |
Yield() |
主动放弃 CPU 时间片,让同优先级线程执行 | |
| 属性 | IsBackground |
设置是否为后台线程(默认 false,前台线程) |
Name |
线程名称(仅调试用) | |
Priority |
线程优先级(Lowest ~ Highest,默认 Normal) | |
IsAlive |
判断线程是否处于运行 / 阻塞状态 | |
| 委托 | ThreadStart |
无参数线程委托(public delegate void ThreadStart()) |
ParameterizedThreadStart |
带参数线程委托(public delegate void ParameterizedThreadStart(object)) |
二、同步工具(解决资源竞争)
| 同步工具 | 核心 API | 用途说明 |
|---|---|---|
| lock 关键字 | lock(lockObj) { 临界区 } |
简洁同步(底层基于 Monitor,单进程内首选) |
| Monitor 类 | Monitor.Enter(lockObj) |
获取锁(需配合 Exit) |
Monitor.TryEnter(lockObj, int ms) |
尝试获取锁(超时返回 bool,避免死锁) | |
Monitor.Exit(lockObj) |
释放锁(必须在 finally 中调用) | |
Monitor.Wait(lockObj) |
释放锁并等待(线程通信) | |
Monitor.Pulse(lockObj) |
唤醒一个等待线程(线程通信) | |
| SemaphoreSlim | new SemaphoreSlim(int initial, int max) |
信号量(控制并发线程数,initial:初始可用数,max:最大可用数) |
Wait() / Wait(int ms) |
获取信号量(无可用则阻塞) | |
Release() |
释放信号量(可用数 +1) | |
| Mutex | new Mutex(bool initOwn, string name) |
互斥锁(支持跨进程,name:全局唯一名称) |
WaitOne(int ms) |
等待获取锁(超时返回 bool) | |
ReleaseMutex() |
释放锁 | |
| ReaderWriterLockSlim | EnterReadLock() |
获取读锁(共享,多线程可同时读) |
ExitReadLock() |
释放读锁 | |
EnterWriteLock() |
获取写锁(排他,仅单线程可写) | |
ExitWriteLock() |
释放写锁 |
三、线程池(ThreadPool)
| 核心 API | 用途说明 |
|---|---|
ThreadPool.QueueUserWorkItem(WaitCallback) |
提交无参数任务到线程池(WaitCallback 委托:void Method(object)) |
ThreadPool.QueueUserWorkItem(WaitCallback, object) |
提交带参数任务到线程池 |
ThreadPool.GetMinThreads(out int worker, out int io) |
获取最小线程数(worker:工作线程,io:IO 完成端口线程) |
ThreadPool.SetMinThreads(int worker, int io) |
设置最小线程数(IO 密集型任务可增大) |
ThreadPool.GetMaxThreads(out int worker, out int io) |
获取最大线程数 |
四、Task 与异步编程(推荐方案)
1. Task 创建与启动
| API 语法 | 用途说明 |
|---|---|
Task.Run(Action) |
启动无返回值任务(入线程池,创建即调度) |
Task.Run(Func<TResult>) |
启动有返回值任务(返回 Task<TResult>) |
new Task(Action) / Start() |
手动创建任务(需调用 Start () 启动) |
new Task<TResult>(Func<TResult>) |
手动创建有返回值任务 |
Task.Factory.StartNew(Action, TaskCreationOptions) |
配置化创建任务(如 LongRunning) |
2. 异步等待(async/await)
| 语法 | 用途说明 |
|---|---|
async Task Method() |
无返回值异步方法(不可省略 Task,除非是事件处理) |
async Task<TResult> Method() |
有返回值异步方法 |
await Task |
异步等待任务完成(不阻塞当前线程) |
await Task.Delay(int ms) |
异步休眠(优于 Thread.Sleep,不阻塞线程) |
3. 任务取消(CancellationToken)
| API 语法 | 用途说明 |
|---|---|
new CancellationTokenSource(int ms) |
创建取消令牌源(支持超时自动取消) |
cts.Token |
获取取消令牌(传递给任务) |
cts.Cancel() |
手动触发取消 |
token.ThrowIfCancellationRequested() |
任务内检查取消信号(触发 OperationCanceledException) |
4. 进度报告(IProgress<T>)
| API 语法 | 用途说明 |
|---|---|
new Progress<T>(Action<T> callback) |
创建进度报告器(回调自动在创建线程执行,无需线程切换) |
progress.Report(T value) |
任务内报告进度(T 可是 int / 自定义类) |
5. 批量任务处理
| API 语法 | 用途说明 |
|---|---|
Task.WhenAll(params Task[] tasks) |
等待所有任务完成(返回 Task/Task<TResult[]>) |
Task.WhenAny(params Task[] tasks) |
等待任意一个任务完成(返回最先完成的 Task) |
task.ContinueWith(TaskContinuationOptions) |
任务完成后执行后续操作(任务链) |
6. 异常处理
| 场景 | 核心语法 |
|---|---|
| await 场景 | try { await task; } catch (Exception) { } |
| 非 await 场景 | task.Wait(); → 捕获 AggregateException → ex.Flatten().InnerException |
7. ValueTask<TResult>(.NET Core 2.1+)
| API 语法 | 用途说明 |
|---|---|
async ValueTask<TResult> Method() |
轻量级异步返回(值类型,减少堆分配,适合快速完成的异步操作) |
五、线程安全集合(System.Collections.Concurrent)
| 集合类 | 核心 API | 用途说明 |
|---|---|---|
| ConcurrentDictionary<TKey,TValue> | TryAdd(TKey, TValue) |
尝试添加键值对(线程安全,失败返回 false) |
GetOrAdd(TKey, Func<TKey,TValue>) |
获取或添加(不存在则执行委托创建值) | |
TryGetValue(TKey, out TValue) |
尝试获取值(线程安全) | |
| ConcurrentQueue<T> | Enqueue(T) |
入队(线程安全) |
TryDequeue(out T) |
出队(失败返回 false,非阻塞) | |
| ConcurrentStack<T> | Push(T) / PushRange(T[]) |
入栈(线程安全) |
TryPop(out T) / TryPopRange(T[]) |
出栈(失败返回 false) | |
| BlockingCollection<T> | Add(T) |
入队(满时阻塞) |
GetConsumingEnumerable() |
出队(空时阻塞,配合 CompleteAdding() 停止) |
|
CompleteAdding() |
标记集合不再添加数据(让消费者退出循环) |
六、线程通信工具
| 通信工具 | 核心 API | 用途说明 |
|---|---|---|
| AutoResetEvent | new AutoResetEvent(bool initialState) |
自动重置事件(initialState:初始信号状态,Set() 后自动重置无信号) |
WaitOne() / WaitOne(int ms) |
等待信号(阻塞) | |
Set() |
发送信号(唤醒一个线程) | |
| ManualResetEventSlim | new ManualResetEventSlim(bool initialState) |
手动重置事件(轻量级,Set() 后需 Reset() 重置) |
Set() / Reset() |
发送信号 / 重置信号 | |
| CountdownEvent | new CountdownEvent(int count) |
倒计时门闩(count:需调用 Signal() 的次数) |
Signal() |
倒计时减 1 | |
Wait() |
阻塞直到倒计时为 0 | |
| Barrier | new Barrier(int participantCount, Action<Barrier>) |
循环屏障(participantCount:参与线程数,回调在所有线程到达后执行) |
SignalAndWait() |
通知屏障并等待其他线程 |
七、常用辅助类 / 方法
| 类 / 方法 | 用途说明 |
|---|---|
Thread.CurrentThread |
获取当前执行的线程实例 |
Environment.ProcessorCount |
获取 CPU 核心数(线程数调优用) |
CancellationToken.None |
空取消令牌(无需取消的任务使用) |
Task.CompletedTask |
已完成的空任务(异步方法返回 "成功完成" 时使用) |
八、场景 - API 快速映射
| 开发场景 | 推荐 API |
|---|---|
| 简单多线程(需控制优先级 / 后台线程) | Thread 类 |
| 单进程内共享资源同步 | lock 关键字 |
| 限流 / 控制并发数 | SemaphoreSlim |
| 跨进程同步(如多程序共享文件) | Mutex |
| 读多写少场景(如缓存) | ReaderWriterLockSlim |
| 生产者 - 消费者(简单版) | BlockingCollection<T> |
| 生产者 - 消费者(基于锁通信) | Monitor.Pulse() / Monitor.Wait() |
| 等待多个线程完成 | CountdownEvent |
| 多个线程分阶段协作 | Barrier |
| 短期轻量级任务(无返回值) | ThreadPool.QueueUserWorkItem |
| 异步任务(有返回值 / 取消 / 进度) | Task + async/await |
| 快速完成的异步操作(性能优化) | ValueTask<TResult> |
| 多线程读写字典 / 队列 | ConcurrentDictionary / ConcurrentQueue |
希望对大家有所帮助。感谢大家的关注和点赞。