文章目录
- 一、Thread
- 二、线程创建与启动
- 三、线程生命周期控制
- 四、线程锁🔒
- (一)lock
- (二)Monitor类
- (三)其他
- 五、线程池(ThreadPool)
- (一)Thread vs ThreadPool
- (二)使用示例
- 六、Unity中的特殊考量
- 七、高级用法
- (一)线程本地存储
- (二)异步委托
- (三)取消模式
- 八、最佳实践建议
- 九、注意事项
一、Thread
Thread类是 .NET 框架中用于创建和管理线程的核心类,它让我们能够直接控制操作系统层面的线程。简单来说,线程就是程序里并行执行任务的“工人”,而 Thread 类就是管理这些工人的“调度员”。
线程
- 操作系统调度的最小执行单元,可以理解为 CPU 分配时间的最小单位
- 共享进程资源,但拥有独立的执行路径,就像多个工人共用一间办公室,但各干各的活
- 每个线程维护自己的调用栈和寄存器状态,相当于每个工人都有自己的工具箱和记事本状态
Thread属性
| 属性 | 类型 | 说明 |
|---|---|---|
IsAlive | bool | 线程是否正在运行 |
IsBackground | bool | 是否为后台线程(后台线程不会阻止程序退出) |
Name | string | 线程名称(调试时方便识别) |
Priority | ThreadPriority | 线程调度优先级(越高越优先执行) |
ThreadState | ThreadState | 当前线程状态(如运行中、已停止等) |
二、线程创建与启动
创建
// 通过ThreadStart委托创建Threadthread1=newThread(newThreadStart(SimpleMethod));// 通过Lambda表达式创建(常用)Threadthread2=newThread(()=>{Debug.Log("线程执行中");});// 带参数的线程(使用ParameterizedThreadStart)Threadthread3=newThread(ParameterizedMethod);thread3.Start("参数数据");启动
thread1.Start();// 无参数启动thread3.Start("参数");// 带参数启动三、线程生命周期控制
线程状态管理
// 暂停当前线程(毫秒)Thread.Sleep(1000);// 等待线程结束thread1.Join();// 无限等待thread1.Join(500);// 最多等待500ms// 终止线程(不推荐)thread1.Abort();// 已过时方法,会引发ThreadAbortException线程优先级设置
thread1.Priority=ThreadPriority.Highest;// 提高优先级优先级等级:
HighestAboveNormalNormal (默认)BelowNormalLowest
四、线程锁🔒
当多个线程同时访问同一个共享资源(比如一个全局变量、一个文件)时,就会像多人同时抢同一支笔写字一样,造成数据错乱。lock 和 Monitor 就是用来确保同一时刻只有一个线程能进入特定代码块的“门禁系统”,谁先进去,谁就先办事,办完出来下一个再进。
(一)lock
lock 是 C# 提供的一个语法糖,是使用 Monitor 类最简洁、最安全的方式。它简单、不易出错,编译器会自动帮你处理 Monitor.Enter 和 Monitor.Exit 的配对,即使代码抛异常,也能确保锁被释放,不会出现“锁死”的情况。释放。
privateobject_lockObj=newobject();voidThreadSafeMethod(){lock(_lockObj){// 临界区代码}}(二)Monitor类
Monitor是lock 底层的、功能更丰富的工具。提供了 lock 所不具备的高级功能,比如尝试进入锁(TryEnter),以及实现线程间的等待/脉冲(Wait/Pulse)机制。当你需要更精细的控制,而不是简单的互斥时使用。
Monitor.Enter(_lockObj);try{// 临界区代码}finally{Monitor.Exit(_lockObj);}(三)其他
// 信号量Semaphoresemaphore=newSemaphore(3,3);// 允许3个并发// 互斥体Mutexmutex=newMutex();// 读写锁ReaderWriterLockSlimrwLock=newReaderWriterLockSlim();五、线程池(ThreadPool)
跟对象池相似的还有线程池ThreadPool,都是为了减少频繁创建时减少的性能开销。
(一)Thread vs ThreadPool
| 特性 | Thread | ThreadPool |
|---|---|---|
| 创建开销 | 大 | 小 |
| 数量控制 | 手动 | 自动管理 |
| 生命周期 | 手动管理 | 自动回收 |
| 适用场景 | 长时间任务 | 短时间任务 |
| 优先级设置 | 支持 | 不支持 |
(二)使用示例
ThreadPool.QueueUserWorkItem(state=>{// 后台执行的任务Debug.Log($"线程池线程ID:{Thread.CurrentThread.ManagedThreadId}");});六、Unity中的特殊考量
Unity API线程限制
// 错误示例:Threadthread=newThread(()=>{GameObject.CreatePrimitive(PrimitiveType.Cube);// 会报错});// 正确做法:通过主线程调度器UnityMainThreadDispatcher.Instance.Enqueue(()=>{GameObject.CreatePrimitive(PrimitiveType.Cube);});线程安全日志
voidThreadLog(stringmessage){lock(consoleLock){Debug.Log($"[Thread{Thread.CurrentThread.ManagedThreadId}]{message}");}}协程与线程结合
IEnumeratorThreadCoroutine(){booldone=false;stringresult=null;Threadthread=newThread(()=>{result=HeavyCalculation();done=true;});thread.Start();while(!done){yieldreturnnull;}Debug.Log($"计算结果:{result}");}七、高级用法
(一)线程本地存储
ThreadLocal<int>threadLocalValue=newThreadLocal<int>(()=>42);voidThreadMethod(){Debug.Log(threadLocalValue.Value);// 每个线程独立的值}(二)异步委托
Func<int,int,int>add=(a,b)=>a+b;IAsyncResultresult=add.BeginInvoke(2,3,null,null);intsum=add.EndInvoke(result);// 获取结果(三)取消模式
CancellationTokenSourcects=newCancellationTokenSource();Threadthread=newThread(()=>{while(!cts.IsCancellationRequested){// 可取消的任务}});thread.Start();// 需要取消时cts.Cancel();八、最佳实践建议
命名线程:调试时便于识别
thread.Name="NetworkThread";异常处理:线程内必须捕获异常
newThread(()=>{try{/* 代码 */}catch(Exceptionex){/* 处理 */}}).Start();资源清理:确保释放线程持有的资源
using(varresource=newDisposableResource()){newThread(()=>{resource.DoWork();}).Start();}避免Abort:使用协作式取消替代强制终止
性能考量:
- 线程创建开销大约在1-10毫秒
- 每个线程默认占用1MB栈空间
- 推荐使用
ThreadPool处理短任务
九、注意事项
Thread类提供了底层的线程控制能力,但在现在的C#日常开发中,多数场景推荐使用更高级的Task和async/await模式。只有在需要精细控制线程特性(如优先级、命名等)时,才应该直接使用Thread类。
在Unity中尤其需要注意:
- 主线程才能调用Unity API
- 使用
lock时要避免死锁 - 长时间运行的线程应该设置为后台线程
- 多线程访问Unity对象需格外小心