使用线程
用来创建和维护线程的基类是Thread。它拥有Start, Stop, Resume, Abort, Suspend和Join (wait for)等方法让你操纵线程,还可以通过如Sleep, IsAlive, IsBackground, Priority, ApartmentState和ThreadState等方法查询和设置线程状态。
注意:要记住大部分的Thread成员都是虚成员,因此只可以由一个特定Thread类的实例访问。要维护一个特定的线程,你可以创建一个新的Thread类实例,或者通过CurrentThread属性得到当前Thread的一个引用。例外的是Sleep方法,它可让当前的线程挂起指定的毫秒数。
为了启动一个新的线程,你必须指定一个入口以便开始执行该线程。要求是该方法(可以是一个对象上的方法或者是一个模块中的方法)没有参数,并且要定义为一个Sub过程。在同一个对象内,以一个独立的线程来执行一个方法也是可能的。
例如,看以下的代码段。在这个例子中,Instructors类的GetPhotos方法在一个独立的线程上执行。这个方法(没有显示)向数据库查询全部的教师图象,并且将每幅图象以文件的方式保存下来,在这里,数据库访问和文件访问在一个分开的线程上执行。
Dim tPhoto As Thread Dim tsStart As ThreadStart Dim objIns As New Instructors
tsStart = New ThreadStart(AddressOf objIns.GetPhotos) tPhoto = New Thread(tsStart)
tPhoto.Priority = ThreadPriority.BelowNormal tPhoto.Name = "SavingPhotos"
tPhoto.Start()
' Wait for the started thread to become alive While (tPhoto.ThreadState = ThreadState.Unstarted) Thread.Sleep(100) End While
...
If tPhoto.IsAlive Then MsgBox("Still processing images...") MsgBox("Waiting to finish processing images...") tPhoto.Join End If
MsgBox("Done processing images.") |
在上面的代码中,你可以看到启动一个线程包括实例化一个ThreadStart委派,并且通过AddressOf操作符将入口地址传送给它。该委派然后就会传送给Thread类的构造器。在线程真正开始执行前,优先权被设置为BelowNormal,这样主线程将可更迅速地响应请求。虽然Win32 API支持30个优先权级别,不过在ThreadPriority枚举中,你只有4个其它的优先级可以设置 (AboveNormal, Highest, Lowest和Normal) 。
注意:ThreadPriority枚举对象和Win32 API的32个级别是有对应关系的,实际上,最低的优先权(Lowest)对应6,而最高的(Highest)为10。
然后代码就设置了线程的Name属性,开始看来有点奇怪,因为一个线程或者是它的名字应该永远都不会在用户的界面上出现,这个名字其实是出现在调试器中,也可用作日志的用途。接着就是执行Start方法来真正开始执行。
技巧 有时得到线程的一个数字标识来作日志和汇报目的是非常方便的。你可以调用CurrentThread属性或者Thread类上的GetHashCode方法。这将会返回一个数字,你可以用它来在应用中作记录或者事件日志。
启动线程后,代码就进入一个循环等待,检查ThreadState属性的值是否为Unstarted(这是线程的初始状态),直到线程启动。ThreadState枚举还包括有9个其它的状态,由Running到Stopped。要注意的是调用Thread类的共享方法Sleep将会令该线程休眠指定的毫秒数,在这里是主线程而不是tPhoto表示的线程。最后,在执行一些其它的工作后,主线程通过检查IsAlive属性来看tPhoto是否仍然运行。如果是的话,就会在调用Join方法前,向用户展示相应的信息。该方法通过阻塞来同步两个线程(挂起当前执行的线程)。直到调用该方法的线程停下来为止。
技巧 与上面提到的Priority属性无关,CLR会区分前台运行的线程和后台运行的线程。如果一个线程被标识为后台线程,CLR在AppDomain关闭的时候并不会等待它完成。如前面讨论的那样,在使用异步文件IO时,运行时创建的线程都是后台的线程,因此你要确保代码的主线程不会在I/O完成前退出。默认的情况下,上面创建的线程被标识为前台,同时它们的IsBackgropu属性被设置为False。
虽然在代码中并没有展示,不过在线程执行的时候它可以通过Suspend方法挂起,然后通过Resume继续执行。此外线程还可以通过使用Abort方法退出,这时将会在线程内抛出一个例外。