C#.Net-Thread-学习笔记一、多线程基础概念多线程的三大特点异步执行不阻塞主线程多件事可以同时进行效率高充分利用 CPU 等计算机资源无序性多个线程的执行顺序不可预测无法控制⚠️ 正因为无序性多线程调试困难通常只能通过写日志、输出结果、结合线程 ID 来分析问题。// 获取当前线程 ID用于区分不同线程 int id Thread.CurrentThread.ManagedThreadId;二、Thread 类基本概念来源System.Threading命名空间出现时间.NET Framework 1.0类型密封类(sealed class)不可被继承作用操作计算机线程资源的帮助类库开启线程方式一ThreadStart(无参数)ThreadStart threadStart new ThreadStart(() { DoSomething(Richard); }); Thread thread new Thread(threadStart); thread.Start();方式二ParameterizedThreadStart(带参数)ParameterizedThreadStart pts new ParameterizedThreadStart(obj { Console.WriteLine($参数: {obj}); }); Thread thread new Thread(pts); thread.Start(传递的参数);常用 API线程控制thread.Suspend(); // 暂停线程(不会立即停止) thread.Resume(); // 恢复执行 thread.Abort(); // 停止线程(向线程抛出异常) Thread.ResetAbort(); // 取消停止让线程继续执行❌Suspend、Resume、Abort均已标记为过时(Obsolete)实际开发中不应使用。线程无法真正从外部被强制终止这些方法只是通过异常机制间接干预存在安全隐患。线程等待方式一观望式轮询(不推荐)while (thread.ThreadState ! ThreadState.Stopped) { Thread.Sleep(500); // 每 500ms 检查一次 }❌ 持续轮询会浪费 CPU 时间片不推荐。方式二Join 等待(推荐)thread.Join(); // 无限等待直到线程结束 thread.Join(500); // 最多等待 500ms过时不候 thread.Join(TimeSpan.FromMilliseconds(500)); // 同上TimeSpan 写法✅Join()是最简洁直接的线程等待方式。前台线程 vs 后台线程thread.IsBackground true; // 后台线程程序关闭时线程立即终止 thread.IsBackground false; // 前台线程程序关闭时等待线程执行完毕再退出默认通过new Thread()创建的线程是前台线程ThreadPool中的线程全部是后台线程主线程(Main 方法所在线程)是前台线程⚠️ 如果忘记将长时间运行的线程设为后台线程可能导致程序关闭后进程仍然存活。线程优先级thread.Priority ThreadPriority.Highest; // 最高优先级 thread.Priority ThreadPriority.Lowest; // 最低优先级⚠️ 设置优先级只是提高被优先调度的概率操作系统不保证严格按优先级执行不能依赖它来控制执行顺序。三、Thread 扩展封装问题背景多线程天然无序但有时需要两个操作都在新线程中执行同时保证它们的执行顺序方案一顺序执行两个委托把两个委托放进同一个线程天然保证顺序private void CallBackThread(ThreadStart threadStart, Action action) { Thread thread new Thread(() { threadStart.Invoke(); // 先执行 action.Invoke(); // 后执行 }); thread.Start(); }方案二带返回值的异步执行核心思路立即开启新线程执行返回一个取结果的委托调用方在需要结果时才触发等待。private FuncT CallBackFuncT(FuncT func) { T result default; Thread thread new Thread(() { result func.Invoke(); // 新线程中执行 }); thread.Start(); // 立即开启不阻塞 // 返回一个延迟取值的委托 return new FuncT(() { thread.Join(); // 调用时才等待线程完成 return result; }); }使用示例Funcint asyncFunc CallBackFunc(() { Thread.Sleep(5000); return DateTime.Now.Year; }); // 这里不阻塞可以继续做其他事 Console.WriteLine(继续执行其他任务...); // 需要结果时才等待 int result asyncFunc.Invoke();⚠️.NET Core中Delegate.BeginInvoke/EndInvoke已不再支持不能用于异步执行委托需要用上述方式或Task替代。四、ThreadPool 线程池为什么需要线程池Thread的问题API 繁多控制复杂线程数量需要程序员手动管理容易滥用频繁创建/销毁线程系统开销大ThreadPool的优势.NET Framework 2.0引入采用池化思想预先创建线程放入池中需要时直接取用用完自动归还自动管理线程数量防止资源耗尽API 更简洁去掉了 Thread 中不必要的方法申请线程执行任务ThreadPool.QueueUserWorkItem(obj { DoSomething(obj.ToString()); }, 参数);线程等待ManualResetEventThreadPool 没有Join()需要用ManualResetEvent实现等待。ManualResetEvent resetEvent new ManualResetEvent(false); // 初始为未触发 ThreadPool.QueueUserWorkItem(obj { DoSomething(obj.ToString()); resetEvent.Set(); // 任务完成发出信号 }, Richard); // 主线程可以继续做其他事 Console.WriteLine(主线程继续执行...); resetEvent.WaitOne(); // 阻塞直到 Set() 被调用 Console.WriteLine(子线程已完成);工作原理new ManualResetEvent(false)— 初始状态为未触发子线程任务完成后调用Set()— 状态变为已触发主线程调用WaitOne()— 阻塞等待直到状态变为已触发才继续控制线程数量ThreadPool.SetMinThreads(4, 4); // 设置最小工作线程数和 IO 线程数 ThreadPool.SetMaxThreads(8, 8); // 设置最大工作线程数和 IO 线程数 ThreadPool.GetMinThreads(out int minWorker, out int minIO); ThreadPool.GetMaxThreads(out int maxWorker, out int maxIO);第一个参数工作线程数(Worker Threads)用于 CPU 密集型任务第二个参数IO 线程数(Completion Port Threads)用于 IO 操作❌强烈不建议手动设置线程池大小。原因这是进程级别的全局设置Task、Parallel、async/await底层都使用线程池修改后会影响所有这些功能设置不当容易引发性能问题甚至死锁五、Thread vs ThreadPool 对比特性ThreadThreadPool出现时间.NET 1.0.NET 2.0线程管理手动创建和销毁自动管理复用线程API 复杂度复杂功能多简单易用性能频繁创建销毁开销大复用线程性能好线程等待Join()ManualResetEvent默认线程类型前台线程后台线程适用场景需要精细控制的长时间任务大量短时间并发任务六、注意事项与最佳实践✅ 短时间任务优先使用ThreadPool✅ 使用Join()进行 Thread 的线程等待✅ 使用ManualResetEvent进行 ThreadPool 的线程同步✅ 长时间运行的线程记得设置IsBackground true❌ 避免使用Abort()、Suspend()、Resume()❌ 不要手动设置线程池大小❌ 不要在生产环境依赖线程优先级来控制执行顺序⚠️ 多个线程访问共享资源时需要加锁(lock、Monitor等)否则会出现数据竞争问题。⚠️ 线程内部的异常不会自动传播到主线程必须在线程内部自行处理。⚠️ 在实际项目中更推荐使用Task和async/await它们是对线程池的更高层封装代码更简洁异常处理和取消机制也更完善。
08-C#
C#.Net-Thread-学习笔记一、多线程基础概念多线程的三大特点异步执行不阻塞主线程多件事可以同时进行效率高充分利用 CPU 等计算机资源无序性多个线程的执行顺序不可预测无法控制⚠️ 正因为无序性多线程调试困难通常只能通过写日志、输出结果、结合线程 ID 来分析问题。// 获取当前线程 ID用于区分不同线程 int id Thread.CurrentThread.ManagedThreadId;二、Thread 类基本概念来源System.Threading命名空间出现时间.NET Framework 1.0类型密封类(sealed class)不可被继承作用操作计算机线程资源的帮助类库开启线程方式一ThreadStart(无参数)ThreadStart threadStart new ThreadStart(() { DoSomething(Richard); }); Thread thread new Thread(threadStart); thread.Start();方式二ParameterizedThreadStart(带参数)ParameterizedThreadStart pts new ParameterizedThreadStart(obj { Console.WriteLine($参数: {obj}); }); Thread thread new Thread(pts); thread.Start(传递的参数);常用 API线程控制thread.Suspend(); // 暂停线程(不会立即停止) thread.Resume(); // 恢复执行 thread.Abort(); // 停止线程(向线程抛出异常) Thread.ResetAbort(); // 取消停止让线程继续执行❌Suspend、Resume、Abort均已标记为过时(Obsolete)实际开发中不应使用。线程无法真正从外部被强制终止这些方法只是通过异常机制间接干预存在安全隐患。线程等待方式一观望式轮询(不推荐)while (thread.ThreadState ! ThreadState.Stopped) { Thread.Sleep(500); // 每 500ms 检查一次 }❌ 持续轮询会浪费 CPU 时间片不推荐。方式二Join 等待(推荐)thread.Join(); // 无限等待直到线程结束 thread.Join(500); // 最多等待 500ms过时不候 thread.Join(TimeSpan.FromMilliseconds(500)); // 同上TimeSpan 写法✅Join()是最简洁直接的线程等待方式。前台线程 vs 后台线程thread.IsBackground true; // 后台线程程序关闭时线程立即终止 thread.IsBackground false; // 前台线程程序关闭时等待线程执行完毕再退出默认通过new Thread()创建的线程是前台线程ThreadPool中的线程全部是后台线程主线程(Main 方法所在线程)是前台线程⚠️ 如果忘记将长时间运行的线程设为后台线程可能导致程序关闭后进程仍然存活。线程优先级thread.Priority ThreadPriority.Highest; // 最高优先级 thread.Priority ThreadPriority.Lowest; // 最低优先级⚠️ 设置优先级只是提高被优先调度的概率操作系统不保证严格按优先级执行不能依赖它来控制执行顺序。三、Thread 扩展封装问题背景多线程天然无序但有时需要两个操作都在新线程中执行同时保证它们的执行顺序方案一顺序执行两个委托把两个委托放进同一个线程天然保证顺序private void CallBackThread(ThreadStart threadStart, Action action) { Thread thread new Thread(() { threadStart.Invoke(); // 先执行 action.Invoke(); // 后执行 }); thread.Start(); }方案二带返回值的异步执行核心思路立即开启新线程执行返回一个取结果的委托调用方在需要结果时才触发等待。private FuncT CallBackFuncT(FuncT func) { T result default; Thread thread new Thread(() { result func.Invoke(); // 新线程中执行 }); thread.Start(); // 立即开启不阻塞 // 返回一个延迟取值的委托 return new FuncT(() { thread.Join(); // 调用时才等待线程完成 return result; }); }使用示例Funcint asyncFunc CallBackFunc(() { Thread.Sleep(5000); return DateTime.Now.Year; }); // 这里不阻塞可以继续做其他事 Console.WriteLine(继续执行其他任务...); // 需要结果时才等待 int result asyncFunc.Invoke();⚠️.NET Core中Delegate.BeginInvoke/EndInvoke已不再支持不能用于异步执行委托需要用上述方式或Task替代。四、ThreadPool 线程池为什么需要线程池Thread的问题API 繁多控制复杂线程数量需要程序员手动管理容易滥用频繁创建/销毁线程系统开销大ThreadPool的优势.NET Framework 2.0引入采用池化思想预先创建线程放入池中需要时直接取用用完自动归还自动管理线程数量防止资源耗尽API 更简洁去掉了 Thread 中不必要的方法申请线程执行任务ThreadPool.QueueUserWorkItem(obj { DoSomething(obj.ToString()); }, 参数);线程等待ManualResetEventThreadPool 没有Join()需要用ManualResetEvent实现等待。ManualResetEvent resetEvent new ManualResetEvent(false); // 初始为未触发 ThreadPool.QueueUserWorkItem(obj { DoSomething(obj.ToString()); resetEvent.Set(); // 任务完成发出信号 }, Richard); // 主线程可以继续做其他事 Console.WriteLine(主线程继续执行...); resetEvent.WaitOne(); // 阻塞直到 Set() 被调用 Console.WriteLine(子线程已完成);工作原理new ManualResetEvent(false)— 初始状态为未触发子线程任务完成后调用Set()— 状态变为已触发主线程调用WaitOne()— 阻塞等待直到状态变为已触发才继续控制线程数量ThreadPool.SetMinThreads(4, 4); // 设置最小工作线程数和 IO 线程数 ThreadPool.SetMaxThreads(8, 8); // 设置最大工作线程数和 IO 线程数 ThreadPool.GetMinThreads(out int minWorker, out int minIO); ThreadPool.GetMaxThreads(out int maxWorker, out int maxIO);第一个参数工作线程数(Worker Threads)用于 CPU 密集型任务第二个参数IO 线程数(Completion Port Threads)用于 IO 操作❌强烈不建议手动设置线程池大小。原因这是进程级别的全局设置Task、Parallel、async/await底层都使用线程池修改后会影响所有这些功能设置不当容易引发性能问题甚至死锁五、Thread vs ThreadPool 对比特性ThreadThreadPool出现时间.NET 1.0.NET 2.0线程管理手动创建和销毁自动管理复用线程API 复杂度复杂功能多简单易用性能频繁创建销毁开销大复用线程性能好线程等待Join()ManualResetEvent默认线程类型前台线程后台线程适用场景需要精细控制的长时间任务大量短时间并发任务六、注意事项与最佳实践✅ 短时间任务优先使用ThreadPool✅ 使用Join()进行 Thread 的线程等待✅ 使用ManualResetEvent进行 ThreadPool 的线程同步✅ 长时间运行的线程记得设置IsBackground true❌ 避免使用Abort()、Suspend()、Resume()❌ 不要手动设置线程池大小❌ 不要在生产环境依赖线程优先级来控制执行顺序⚠️ 多个线程访问共享资源时需要加锁(lock、Monitor等)否则会出现数据竞争问题。⚠️ 线程内部的异常不会自动传播到主线程必须在线程内部自行处理。⚠️ 在实际项目中更推荐使用Task和async/await它们是对线程池的更高层封装代码更简洁异常处理和取消机制也更完善。