VB.NET编程经过长时间的发展,很多用户都很了解VB.NET编程中多线程程序。多线程成为大多程序员苦恼的事,现在和大家交流一下多线程。多线程是可行的,因为操作系统是多任务的,它有模拟同一时刻运行多个应用程序的能力。尽管多数个人计算机只有一个处理器,但是现在的操作系统还是通过在多个执行代码片断之间划分处理器时间提供了多任务。线程可能是整个应用程序,但通常是应用程序可以单独运行的一个部分。操作系统根据线程的优先级和离最近运行的时间长短给每一个线程分配处理时间。多线程对于时间密集型事务(例如文件输入输出)应用程序的性能有很大的提高。VB.NET编程中通常使用三类等待句柄:互斥对象、ManualResetEvent和AutoResetEvent。后两种通常用于同步事件。
1.互斥对象
互斥对象都是同步对象,它们只能在一个时刻由一个线程拥有。实际上,互斥这个名字衍生自互斥对象的所有权是相互排斥的。当线程请求独占访问某种资源时,它们请求互斥对象的所有权。因为在某个时刻只有一个线程能拥有一个互斥对象,其它线程在使用资源前必须等待互斥对象的所有权。WaitOne方法引发一个调用线程等待互斥对象的所有权。如果拥有互斥对象的线程正常终止,该互斥对象的状态就被设置为signaled,下一个线程获得它的所有权。
2.同步事件
同步事件用于通知其它的线程发生了某种事情或者某种资源可用。不要被它使用了"事件"这个词迷惑了。同步事件与其它的VisualBasic事件不同,它是真正的等待句柄。与其它的等待句柄类似,同步事件有两种状态signaled 和nonsignaled。调用同步事件的某个等待方法的线程必须等待,直到其它线程调用Set方法给事件发信号。有两个同步事件类。线程使用Set方法把ManualResetEvent实例的状态设置为signaled。线程使用Reset方法或控制返回等待WaitOne调用把实例的状态设置为nonsignaled。AutoResetEvent类的实例也可以使用Set设置为signaled,但是只要通知等待线程事件变为signaled,它们自动返回到nonsignaled。
下面的例子使用AutoResetEvent类同步线程池事务。
- SubStartTest()
- DimATAsNewAsyncTest()
- AT.StartTask()
- EndSub
- ClassAsyncTest
- PrivateSharedAsyncOpDoneAsNewSystem.Threading.AutoResetEvent(False)
- SubStartTask()DimTpoolAsSystem.Threading.ThreadPoolDimargAsString="SomeArg"
- Tpool.QueueUserWorkItem(NewSystem.Threading.WaitCallback(_AddressOfTask),arg)'对一个事务进行排队
- AsyncOpDone.WaitOne()'等待该线程调用SetMsgBox("Threadisdone.")
- EndSubSubTask(ByValArgAsObject)
- MsgBox("Threadisstarting.")
- System.Threading.Thread.Sleep(4000)'等待4秒.
- MsgBox("Thestateobjectcontainsthestring"&CStr(Arg))
- AsyncOpDone.Set()'发信号表明该线程完成了
- EndSub
- EndClass
3.监视对象和同步锁
监视对象确保代码块的运行不被运行在其它线程中的代码打断。换句话说,其它线程中的代码不能运行,直到被同步的代码块结束。在VisualBasic .NET中使用SyncLock关键字来简化监视对象的访问。在VisualC# .NET中使用Lock关键字。
例如,假定你有一个程序,它重复地、异步读取数据并显示结果。使用优先多任务操作系统,正在运行的线程可以因为操作系统允许其它的线程运行而被打断。如果没有同步,数据正在显示时,显示数据的对象被其它的线程修改,有可能得到的是部分更新的数据视图。SyncLock保证一段代码持续运行,不被打断。下面的例子显示了怎样使用SyncLock给显示过程提供数据对象的独占访问。
- ClassDataObject
- PublicObjTextAsString
- PublicObjTimeStampAsDate
- EndClassSubRunTasks()
- DimMyDataObjectAsNewDataObject()
- ReadDataAsync(MyDataObject)
- SyncLockMyDataObject
- DisplayResults(MyDataObject)
- EndSyncLock
- EndSub
- SubReadDataAsync(ByRefMyDataObjectAsDataObject)'添加异步读取和处理数据的代码
- EndSubSubDisplayResults(ByValMyDataObjectAsDataObject)'添加显示结果的代码
- EndSub
Interlocked类
你可以使用Interlocked类的方法防止多个线程同时更新或比较同一个值的问题发生。这个类的方法让你安全地增加、减少、交换和比较来自任何线程的值。下面的例子演示了怎样使用Increment方法增加一个运行在独立线程上的多个过程共享的变量的值。
- SubThreadA(ByRefIntAAsInteger)
- System.Threading.Interlocked.Increment(IntA)
- EndSub
- SubThreadB(ByRefIntAAsInteger)
- System.Threading.Interlocked.Increment(IntA)
- EndSub
ReaderWriter锁
在有些情况下,你可能希望只在写数据时锁定资源,在数据没有更新完前允许多个客户同时读数据。某个线程正在修改资源时,ReaderWriterLock类加强了对该资源的独占访问,但是允许读取资源的非独占访问。ReaderWriter锁是排他锁的一个有用的备选方案,排他锁引起其它线程等待,即使这些线程不需要更新数据。下面的例子演示了怎样使用ReaderWriter调整来自多个线程的读和写操作。
- ClassReadWrite
- 'ReadData和WriteData方法可以被多个线程安全地调用
- PublicReadWriteLockAsNewSystem.Threading.ReaderWriterLock()
- SubReadData()'这个过程从数据源读取信息。在允许其它线程调用ReadData时,读取锁放置任何数据写入直到读取完成
- ReadWriteLock.AcquireReaderLock(System.Threading.Timeout.Infinite)Try'此处执行数据操作
- FinallyReadWriteLock.ReleaseReaderLock()'释放读取锁
- EndTry
- EndSubSubWriteData()'这个过程向数据源写信息。写入锁防止数据被读取或者写入知道线程完成写操作。
- ReadWriteLock.AcquireWriterLock(System.Threading.Timeout.Infinite)Try'此处执行写操作
- FinallyReadWriteLock.ReleaseWriterLock()'释放写入锁
- EndTry
- EndSub
- EndClass
结论
多线程处理是可伸缩的、容易响应的应用程序的关键。VB.NET编程支持加强的、多线程开发模型,它使开发者迅速拥有了开发多线程应用程序的能力。
◆VisualBasic .NET使用新的.NET框架组件类,它使建立多线程应用程序更容易。
◆记住尽管多线程能提高性能,但是每个线程有建立线程需要的附加内存和保持它运行需要的处理器时间的花消。
◆线程的属性和方法控制着线程间的交互操作,并且决定什么时候资源可以给运行的线程使用。
◆尽管多线程看起来带来了混乱,但是你可以使用同步技术控制正在运行的线程。
◆尽管多线程增加了应用程序的复杂性,但是它通过高效率分配可用资源提高了应用程序的可伸缩性。
使用本文讨论的技术,你可以开发和处理处理器密集型事务的专业应用程序。
【编辑推荐】