对资源的同步访问
一般来说,你希望在独立的线程中运行各种处理,而不需要访问共享的资源。建议的方法如下:
1、封装要运行的处理到一个类中,并且留一个入口来启动该处理,例如Public Sub Start()并且初始化变量来处理状态
2、创建一个独立的类实例
3、设置处理需要的实例变量
4、在一个独立的线程中调用入口
5、不要引用该类的实例变量
只要使用这个方法,全部的实例变量对于线程都是“私有的”,因此可以无需担心同步的问题。
不过,有时这种情况是不能避免的,例如数据库连接或者文件处理。为了确保某线程在访问这些资源时其它线程处于等待状态,你可以使用Monitor类和它的相关方法,包括有Enter, Exit, TryEnter, Wait, Pulse和PulseAll。
例如,假定上面代码中的Instructors类包含了一个类级的SqlConnection对象,该对象被所有的方法共享,并且用来连接数据库。这就是一个资源共享的例子,它被类中的所有方法所共享。
注意:
虽然使用连接池可提供一个更富扩展性的方案,不过这个例子满足我们当前的需要,它让所有的数据库访问通过一个单一的数据库连接进行。这种方式对于需要一个持久的数据库连接的应用是适合的,不过不适合用在分布式的应用。
这个例子中,我们假设在调用GetPhotos后,客户端继续调用一个使用该连接对象的方法。由于连接可能正在被GetPhotos使用,如果SqlConnection正在忙于处理其它的结果,该方法将会抛出一个例外。
要避免这种情形,GetPhotos方法可以使用Monitor的共享方法在其代码中创建critical section。简单说来,critical section就是调用Monitor类的Enter和Exit方法所构成的代码块,通过它,访问的同步是基于传送至Enter方法的对象。也就是说,如果GetPhotos方法要独立地使用SqlConnection,它必须要创建一个critical section,在该section的开始部分,通过传送SqlConnection到Monitor的Enter方法中,并且在结束的时候调用Exit方法。被传送的对象可以是任何继承System.Object的对象。
如果该对象正在被其它的线程使用,Enter方法将会阻塞直到对象被释放。你也可以调用TryEnter方法,该方法不会阻塞,它只会返回一个布尔值指示该对象是否在使用中。一旦进入critical section,GetPhotos方法可以使用SqlConnection执行一个存储过程,并且将结果写出来。在关闭结果集SqlDataReader后,就会调用Monitor类的Pulse方法,以通知等待队列中的下个线程该对象已经释放了。然后就会将线程移动到ready队列中,以便准备开始处理。PulseAll方法则通知全部的等待线程该对象准备被释放。最后就会调用Exit,从而释放monitor并且结束critical section部分。这部分代码的框架见下。
同步的资源。以下的例子展示了GetPhotos方法将使用Monitor类来确保两个线程不会同时使用SqlConnection对象
Public Sub GetPhotos()
Dim cmSQL As SqlCommand Dim sdrIns As SqlDataReader
Try ' Execute proc cmSQL = New SqlCommand("usp_GetPhotos", mcnSQL) cmSQL.CommandType = CommandType.StoredProcedure
' Enter critical section Monitor.Enter(mcnSQL) ' Alternate code ' Do While Not Monitor.TryEnter(mcnSQL) ' Thread.CurrentThread.Sleep(100) ' Loop sdrIns = cmSQL.ExecuteReader() Catch e As Exception End Try
Do While sdrIns.Read ' Read the data and write it to a binary stream Loop
sdrIns.Close Monitor.Pulse(mcnSQL) Monitor.Exit(mcnSQL) ' Exited critical section
Return End Sub |
很明显,critical sections仅应该在需要的时候创建,因为它们会阻塞线程,从而会影响整体的吞吐量。
要同步线程间共享的实例变量,有一个很简单的技巧,这就是使用Interlocket类。该类包含有共享的Increment和Decrement方法,可以将修改变量和检查结果的操作结合成一个单一的操作。这样做是必需的,因为一个线程可以修改变量的值,在接着检查结果之前,它的运行时间就结束了。在该线程再次运行时,变量的值就有可能被其它的线程修改了。
例如下面的代码增加Instructors类的mPhotosProcessed实例级变量的值:
Interlocked.Increment(mPhotosProcessed) |
Interlocked类还支持Exchange和CompareExchange的方法,它们的作用分别是设置变量为特定的值,或者在该变量等于某个值时才这样做。