C#特性知识图谱-四、多线程
四、多线程
4.1 线程与线程函数
线程是操作系统分配CPU的基本单元。应用程序把要完成的数据处理任务分为多份,把分割出来的工作任务封装为一个函数由线程负责执行。
线程的运行过程体现为线程函数的运行过程。
4.2 线程分类
- 前台线程
默认情况下,所有创建的线程其IsBackground
属性为False,这种线程称为前台线程。CLR会等待所有前台线程结束后才会结束整个进程。 - 后台线程
IsBackground
属性为True的线程被称为后台线程。主线程结束执行时,后台线程会被自动中断执行。
4.3 线程的使用
4.3.1 线程创建
线程由Thread类创建的对象代表。
满足这一委托的实例或静态方法都可以作为线程函数。
4.3.2 线程启动
当线程对象创建以后,调用它的Start()方法进行启动,当线程函数代码执行完毕,线程自然结束。
- 通常情况下,程序中的第一个启动的线程作为主线程,由它负责启动其它线程。
- 在线程函数中使用
Thread.CurrentThead
可获取负责执行此函数的线程对象的引用。 - 每个线程有一个ID,Thread对象的
ManagedThreadId
属性保存了这个值。
4.3.3 线程阻塞(暂停/休眠)
Thread.Sleep()
方法让线程进入阻塞状态。
Thread.SpinSleep()
方法让CPU空转,但线程状态不改变。
4.3.4 线程中止
调用主线程的Abort()
方法提前终止线程。
注意:需要在线程函数体内捕获ThreadAbortException
异常。
4.3.5 线程的Join
Thread类的Join方法可以让一个线程等待另一个线程的结束。
在线程A的执行流程中调用线程B的Join()
方法,其实是相当于把线程B的整个执行流程嵌入到线程A的执行流程中。线程A会在调用Join()
方法的那句代码处等待,直到线程B工作完成。
4.4 线程状态
4.4.1 线程状态(ThreadState)
4.4.2 线程状态转换
4.5 多线程编程技巧
4.5.1 在线程函数中调用有参数和返回值的函数
方法一:外套函数
- 优点:简单直观
- 缺点:
- 字段公有违背面向对象封装原则;
- 如果有多个方法,则字段数目产生爆炸式增长。
方法二:使用ParameterizedThreadStart委托
- 带参数的ParameterizedThreadStart委托声明
public delegate void ParameterizedThreadStart(Object obj)
- 将要传递给线程函数的信息封装为一个对象,然后调用Tread类的构造函数
public Thread (ParameterizedThreadStart start)
- 启动线程时,线程传递一个参数信息对象即可
Thread t=new Thread(new ParameterizedThreadStart(线程函数));
t.Start(封装的线程参数对象);
4.5.2 应用示例
- 单参无返回值函数示例
只有一个参数、返回void的方法,直接吧它的参数类型改为Object即可。
- 多参有返回值的函数示例
通过创建参数辅助类的方式将其进行包装改造,使其满足要求,也可以使用委托达到回调的目的。
4.6 UI线程
4.6.1 UI线程简介
在多线程程序中不允许跨线程直接访问可视化控件。创建可视化对象的线程叫UI线程,它拥有一个消息循环和消息队列。真正具备响应消息能力的是UI线程函数启动的消息循环,由它负责分发消息和调用窗体过程。
窗体负责响应消息的函数称为窗体过程(Windows Procedure)
用户的操作被转换为消息放入系统消息队列。操作系统有一个专门的线程负责从这一队列中取出消息,根据消息的目标对象(窗体句柄),将其移动到创建它的UI线程所对应的消息队列中。
4.6.2 实现跨线程访问可视化控件的方法
- 编程原则
对于UI控件只允许创建它的线程访问。 - 技术要点
工作线程将希望UI控件显示的信息转换为消息,将其插入到UI线程的消息队列中。 - 实现方式
- 使用Control类的Invoke方法。
- 示例
- 向跨线程访问控件的函数传递参数
4.7 BackgroundWorkder组件
BackgroundWorkder
组件非常适合于在后台运行任务、前台显示结果的应用场景,而且提供了完善的取消工作、报告进度和异常处理的功能,解决了跨线程访问可视化组件的问题。因此,在编写拥有可视化界面的多线程程序时,应优先选择BackgroundWorkder
组件。
4.7.1 BackgroundWorkder
组件使用说明
- DoWork事件
处理业务逻辑代码都写在BackgroundWorkder
组件的DoWork
事件响应函数中。 - 启动工作任务
调用BackgroundWorkder
组件的RunWorkerAsync()
方法启动工作任务,此方法会触发DoWork
事件并自动调用其事件响应方法。
- 信息传递
调用RunWorkerAsync()
方法带参重载形式将信息传送给BackgroundWorkder
组件
在public void RunWorkerAsync(Object argument)
DoWork
事件响应方法的参数e中可以通过e.Argument
属性获取外界传入的信息。 - 取回结果
工作任务结束后BackgroundWorkder
组件激发RunWorkerCompleted
事件,在此事件的参数e(为RunWorkerCompletedEventArgs
类型对象)可以取回工作结果。e.Result
:工作任务执行的结果。e.Error
:这是一个Exception
对象,如果工作任务执行过程中没发生一次,则此属性为null,如果发生了异常,e.Error
就是被抛出的异常。e.Cancelled
:如果在工作任务完成前用户取消了操作,则此属性为True,否则此属性为False。
- 取消工作任务
BackgroundWorkder
组件有一个CancelAsync()
方法,调用此方法将会导致BackgroundWorkder
组件的制度属性CancellationPending
为True。在DoWork
事件处理代码中就可以检查此属性值以取消工作任务。
- 报告工作进度
在DoWork
事件响应代码合适的地方调用BackgroundWorkder
组件的ReportProgress
方法,就会激发ProgressChanged
事件,并且调用ReportProgress
方法说设置的参数(包括当前工作完成的百分比和其它附加信息)会被传送到ProgressChanged
事件的事件参数中。
编写ProgressChanged
事件相应代码,从ProgressChanged
事件参数中取出DoWork
事件中传出的信息。
4.7.2 总结
- 在
DoWork
事件中编写代码完成要以多线程方式进行的工作。 - 调用
RunWorkerAsync()
方法启动多线程执行,此方法会同事激发DoWork
事件。 - 响应
RunWorkerCompleted
事件取回工作结果。 - 调用
CancelAsync()
方法设置取消标记属性CancellationPending=True
,然后在DoWork
事件中取消工作任务。 - 在
ProgressChanged
事件中访问UI窗体上的控件,以向外界报告工作进度。 - 当工作处理完成会自动触发
RunWorkerCompleted
事件,在此方法向用报告工作任务结束。
4.8 线程同步问题
4.8.1 线程死锁
死锁表示系统进入一个僵持状态,所有线程都没有执行完毕,但却谁都没办法继续执行。
- 线程交叉等待造成死锁
- 共享资源访问不当造成死锁
所谓共享资源指多个线程可以同时访问的数据结构、文件等信息实体。
4.8.2 多个线程数据存取错误
当多个线程访问同一个数据时,如果不对读和写的顺序做出限定,例如一个线程正在读数据,而另一个线程尝试写数据,则读数据的线程得到的数据为脏数据也可能出错。
4.9 线程同步方法
锁(lock
)主要用于同步多个线程对共享资源的访问,也可用于控制各个线程之间的推进顺序。
4.9.1 使用Monitor对象实现锁
Enter
与Exit
方法必须严格配对,否则可能出现死锁情况。Monitor一般只用于访问引用类型的共享资源,不要将Monitor用于值类型的共享资源!
4.9.2 lock
lock等价于Monitor
使用lock编写线程安全的方法示例:
4.9.3 等待句柄(WaitHandle)
4.9.3.1 WaitHandle
WaitHandle
是抽象类,在实际开发中常用的是它的子类。
//当线程调用某个WaitHandle对象的WaitOne()方法时,如果此对象处于signaled状态,线程可以继续执行,否则线程被阻塞直到其它某个线程将WaitHandle对象置为signaled状态为止。
public virtual bool WaitOne();
//线程可调用WaitHandle类的WaitAll()方法等待多个WaitHandle对象都变成signaled状态。
public static bool WaitAll(WaitHandle[] WaitHandles);
//线程还可调用WaitHandle类的WaitAny()方法等待一组WaitHandle对象中的任一个变为signaled状态。
4.9.3.2 Mutex
Mutex
适用于多个线程互斥访问同一个共享资源。
使用方式:
- 一个线程要访问共享资源必须调用
Mutex
对象的WaitXXX()
系列方法提出申请。 - 当申请得到批准的线程完成了对于共享资源的访问后,它调用Mutex对象的
ReleaseMutex()
方法释放对于共享资源的访问权。
4.9.3.3 Semaphore
当有多个资源需要同步访问时可以使用Semaphore
,Semaphore
对象一次可以使用多个线程投入运行。
使用原理:
Semaphore
类内部维护一个计数器- 当一个线程调用
Semaphore
对象的Wait系统方法时,此计数器减一,只要计数器还是一个正数线程就不会阻塞。 - 当计数器减到0时,再调用
Semaphore
对象WaitXXX()
系列方法的线程将被阻塞。 - 直到有线程调用
Semaphore
对象的Release()
方法增加计数器值,才有可能解除阻塞状态。
4.9.3.4 EventWaitHandle
在一个多线程程序中,如果多个线程在等待绿灯,绿灯一亮这些线程都可以投入运行,红灯一亮所有线程又被阻塞。想要实现这种多个线程走走停停的功能可以使用线程同步事件类EventWaitHandle
来实现。
EventWaitHandle
类派生自WaitHandle
。Set()
方法将自身设置为signaled
状态,相当于绿灯亮。Reset()
方法将自身设置为non-signaled
状态,相当于红灯亮。
ManualResetEvent
:一次允许多个线程投入运行。
AutoResetEvent
:一次仅运行一个线程投入运行。
本文来自博客园,作者:草叶睡蜢,转载请注明原文链接:https://www.cnblogs.com/tjubuntu/p/15438624.html