多线程使应用程序具有在同一时刻处理多个事务的能力。使用多线程,可以一个线程运行用户界面,同时另一个线程在后台做复杂的计算或处理。MicrosoftVisualBasic.NET支持多线程,因此,我们可以轻易的实现这种能力。
不幸的是,多线程也有它不利的一面。任何时候某个应用程序使用的线程多于一个时,如果多个线程在同一时刻试图使用相同的数据或资源,可能出现麻烦。这种情况一旦出现,程序将变得非常复杂并且难以调试。
更糟的是多线程代码经常在最初开发时运行良好,在形成产品时却往往失败,原因在于有未被发现的多个线程与相同的数据或资源相互作用的情况。这使得多线程编程非常危险。
由于设计和调试多线程应用程序的困难性,微软提出了单线程单元(STA)的概念。VisualBasic6代码通常在STA中运行,因此代码只需要考虑一个线程。这完全的避免了共享数据和资源的问题,但也意味着我们如果不使用其它技术就无法实现多线程。
在.NET中没有类似STA的东西。所有的.NET代码在AppDomain中运行,而AppDomain允许使用多线程。这意味着VisualBasic.NET代码也在AppDomain中运行,因此我们从多线程中受益。很明显,任何时候我们设计多线程应用程序,都必须小心编写代码以避免线程之间的冲突。
最简单的避免线程冲突的的方法是使线程之间永远不与相同的数据或资源交互。但这不一定可行,对任何多线程程序来说,避免或最小化共享数据或资源应作为一个目标。
这不但简化了编码和调试,而且增强了性能。为了解决线程间冲突的问题,我们必须使用同步技术,但同步技术常常引起某个线程阻塞或临时停顿,直到另一个线程完成某个动作。阻塞一个线程意味着让它空闲着,没有工作,降低了性能。
在应用程序中使用多线程的原因很多,最普通的原因是要执行一个需要长时间运行的事务,并且希望用户界面与使用者保持响应。 财软联盟,fs119.net
至少,我们通常用一个“取消”按钮来响应用户,这样用户才能表示他们想中断某个事务。
在VisualBasic6中,我们使用DoEvents和计时器控件和其它工作区的宿主来实现这种功能。在VisualBasic.NET中,这非常简单,因为我们可以使用多线程,只要小心,我们实现该功能根本不会增加代码和调试的复杂性。
在多线程环境中成功实现“取消”按钮的关键在于要记得该按钮仅仅请求事务被终止。是后台事务自己在适当的时候停止。
如果我们实现的“取消”按钮直接停止后台处理,可能会在一些敏感操作的中间停止了它,或在它关闭某些类似文件句柄或数据库连接等重要的资源前停止了它。这可能是致命的,会造成死锁,应用程序行为将变得不稳定或直接中断。
作为该方法的代替,“取消”按钮应该是请求后台事务停止。后台事务能在适当时候检查是否有“取消”请求。当后台线程检测到“取消”请求,便释放所有资源,停止临界活动并温和地终止。
尽管查询“取消”操作很重要,但是我们更希望用户界面(UI)能给使用者显示后台处理的状态信息。这种显示可以是文本信息或完成百分比,或两者都有。
在VisualBasic.NET实现“取消”按钮或状态显示所面临的复杂因素在于WindowsForms库不是线程安全(thread-safe)的。这意味着只有创建窗体的线程能访问该窗体和窗体的控件。其它线程不能安全的与该窗体或它的控件交互。
这意味着我们写代码时必须非常小心,保证只有我们的UI线程与该UI交互。为了确信这一点,我们可以建立一个简单的框架来管理后台工作线程与该UI线程的交互。如果我们做对了,结果便是我们使用的线程与UI代码和长时间运行事务的代码明显相关。
财软联,盟,fs119.net
财.软联盟.fs119.net
财管.家园.fs119.net
创建在自己的线程上使用自己的数据运行的后台处理的最简单的方法是为该后台处理建立一个对象。虽然这不一定可行,但却是一个好的目标,因为它从根本上简化了多线程应用程序。
如果后台线程在自己的对象中运行,它可以使用该对象的实例变量(在类中声明的变量),而不需担心它们被其它的线程使用了。例如,考虑下面的类:
PrivatemInnerAsInteger
PrivatemOuterAsInteger
PublicSubNew(ByValInnerSizeAsInteger,ByValOuterSizeAsInteger)
mInner=InnerSize
mOuter=OuterSize
EndSub
PublicSubWork()
DiminnerIndexAsInteger
DimouterIndexAsInteger
DimvalueAsDouble
ForouterIndex=0TomOuter
ForinnerIndex=0TomInner'dosomecoolcalculationhere
value=Math.Sqrt(CDbl(innerIndex-outerIndex))
Next
Next
EndSub
EndClass
该类设计用于运行后台线程,能使用下面的代码来执行它:
DimbackThreadAsNewThread(AddressOfmyWorker.Work)
backThread.Start()
Worker类有用于存放数据的实例变量。这些变量(mInner和mOuter),可以被后台线程安全地使用,并可以断言它们不会在同一时刻被其它线程访问。
我们可以使用constructor方法来初始化该对象。在后台线程被载入前,主应用程序代码建立该对象的一个实例,并用后台线程操作的数据来初始化。
将该对象的Work方法的地址给后台线程后,它才被载入。该线程将使用对象中给定的数据运行的代码。
由于该对象是自我包含(self-contained)的,我们能建立多个对象,每一个在它们自己的线程上运行并且彼此隔离。
这种实现并不理想。UI没有办法知道后台处理的状态。我们也没有执行任何机制使得UI可以查询到后台处理已经终止了。
所有这些情况都需要后台线程以某种方式与UI线程交互。这种交互很复杂,因此如果我们能用某种方式抽象化这种交互,将它们放入一个类中将会很有利,这样UI和工作代码都不需要担心细节。 财管家园,fs119.net
财,软联盟,fs119.net
我们可以建立一个体系结构来保护UI和工作代码,防止它们进行线程间交互。事实上,我们可以实现一个构架来执行那些复杂代码,我们能使用该构架来管理或控制后台线程与UI的交互。
首先让我们讨论该体系结构,然后再设计和实现该代码。
典型的情况下,一个应用程序以一个线程开始,该线程打开用户界面。为了直观我们称该线程为UI线程。在很多应用程序中这是唯一的线程,因此它控制UI并做所有的处理。
在本例中,我们将建立一个工作线程从事后台处理,让UI线程聚焦于用户界面,这样工作线程在忙于工作时,UI线程仍然可以响应用户。
在UI线程与工作线程之间我们插入一层代码作为UI和工作代码间的接口。该代码本质上是控制器(Controller),管理和控制工作线程与UI线程间的交互。
图1:UI线程,控制器和工作线程
控制器将包含所有这些代码:安全地开始工作线程,将工作线程的任何状态信息传递给UI线程,将任何“取消”请求从UI线程传递给工作线程。UI代码与工作代码并不直接交互;它们通过控制器代码交互。
财软,联盟,fs119.net
财管家园,fs119.net
财管,家园,fs119.net
1、建立Worker对象。
2、初始化Worker对象。
3、调用控制器启动工作线程。
a.Worker对象能通过控制器向UI发送状态信息。
b.UI能通过控制器向Worker对象发送“取消”请求。
4、当Worker对象结束后它通过控制器通知UI。
5、值可以直接从Worker对象检索到。
除了工作线程活动时,UI代码不能直接与Worker对象交互外,对UI来说没有特殊的代码需求。当后台处理在运行时UI保持活动并响应用户。
Worker对象角度的事件流程如下:
1、UI代码创建Worker对象。
2、UI代码使用需要的值来初始化Worker对象。
3、控制器创建后台线程并调用Worker对象的方法。
a.Worker对象运行工作代码。
b.对象将任何状态信息传递给控制器,这样控制器才能将信息传递给UI。
c.适当时,Worker对象查看是否有“取消”请求,如果有,就停止。
d.当Worker对象完成后,它告诉控制器工作已经结束,这样控制器可以将该信息传递给UI。
4、现在工作线程终止,UI能直接与Worker对象交互。
由于工作代码只与控制器交互,我们不用担心工作线程突然与UI组件交互,而这种偶然的交互可能影响应用程序。另外,工作代码依赖于控制器才能与UI线程正确地通信,因此它们都是安全的。
这意味着我们在工作代码中不需要处理任何线程的问题,而只需要处理Worker对象的实例变量。
考虑不同组件,特别是在不同线程上的不同组件之间的交互通常最好使用图。Microsoft?Visio?支持建立UML(通用建模语言)图,它对我们非常有帮助。
下面是阐明UI、Worker对象和Controller之间事件流程的UML序列图。该图假定没有“取消”请求。所有的代码在UI线程上运行。
图2:过程流序列图
了解相同信息的另一种方法是使用UML行为图。这一类图更注重事务而不是对象,因此它显示了事务发生的步骤和过程是怎样一步一步地进行的。我们可以轻易地看出UI代码在左边的线程中工作,而Worker对象在右边的另一个线程中工作。Worker对象在另一个线程中运行之前和之后,它可以被UI直接地使用,进行初始化,然后检索结果。
图3:显示过程流的行为图
使用这类图能帮我们查明后台线程活动时UI与工作线程偶然交互的地方。任何偶然的交互都需要额外的代码来避免bug影响应用程序的稳定性。理想的情况是,这样的交互通过Controller组件发送,在那儿(Controller中)包含使这种交互安全的所有代码。
财 管家园 fs119.net
下图显示了如果UI作出“取消”请求的事件次序。
图4:有“取消”请求的次序图
注意:“取消”请求从UI发送到Controller,直到Worker与Controller检查是否有“取消”请求出现。UI和Controller都不强迫工作代码终止,它们让工作代码在自己地周期内温和并且安全地终止。
财 管家园 fs119.net
为了实现刚才讨论的行为,明显需要实现一个Controller类。为了使这种框架有广泛的用途,我们定义了一些Controller与UI(或客户端)和工作线程交互时使用的形式接口(formalinterface)。
通过为客户端和工作线程定义形式接口,我们能在不同的环境、不同的UI需求和不同的Worker对象中根据需要使用同一个的Controller对象。
下面的UML类图显示了Controller类、IClient和Iworker接口。它也显示了Icontroller接口,任何的工作代码都能使用该接口与Controller对象交互。
图5:Controller和相关接口的类图
IClient接口定义了一些方法,这些方法能被Controller对象调用,在Worker开始或完成时通知客户界面,并将任何中间状态消息通知客户界面。它也包含一个显示工作代码错误的方法。
通常情况下我们利用Controller对象产生并由UI控制的事件来实现这些功能。可是,没有简单的办法使工作线程产生一个事件并被UI线程适当地控制,因此,我们用一系列方法来实现它们。
我们很容易使Controller代码运行在工作线程上,调用UI中的方法,这样使UI线程就拥有它的句柄。
同样,IWorker接口定义的方法将被Controller对象调用来与工作代码交互。当Start方法用于启动处理后台线程时,Initialize方法为工作代码提供一个指向Controller对象的指针。
线程的工作方式使Start方法不能有任何参数。当开始一个新线程时,我们必须给该线程传递一个方法的地址,该方法不能接受参数。
再次,注意没有把Cancel或Stop方法作为IWorker接口的一部分。由于我们不能强迫工作代码停止,因此没有必要;作为代替,工作代码能使用IController接口询问Controller对象是否有“取消”请求。 财软联.盟.fs119.net
IController接口定义了Controller对象的方法,这些方法能被工作代码能调用。它允许工作代码检查Running标志,如果有“取消”请求,该标志为False。它也允许工作代码在完成或者失败时告诉Controller,用状态消息和完成百分比值(从0到100的整数)更新Controller。
最后我们定义Controller对象本身。它有一些能被UI代码调用的方法。其中包括Start,该方法通过给Controller对象提供一个Worker对象的指针启动后台处理。它也包含Cancel方法,用于请求一个“取消”操作。UI也能检查Running属性,看是否有“取消”请求,Percent属性用于显示事务完成的百分比。
Controller类包含constructor方法,接受IClient作为参数,允许UI给Controller提供一个指向窗体的指针,该窗体将实现从Worker收到的所有消息的显示。
为了实现动态点显示活动,我们将建立一个简单的Windows窗体控件,使用计时器改变一组PictureBox控件的颜色。
财管家,园,fs119.net
财,软联盟,fs119.net财管 家园 fs119.net
我们将在一个类库项目中实现框架,这样就可以在任何需要执行后台处理的应用程序中使用它。
打开VisualStudio.NET并建立一个叫做Background的新类库应用程序。因为该库包含一个Windows窗体控件和窗体,我们需要使用AddReferences对话框引用System.Windows.Forms.dll和System.Windows.Drawing.dll。此外,我们使用图6中显示的项目属性对话框可以导入这些全项目(project-wide)的名字空间。
图6:使用项目属性添加全项目名字空间的导入
这完成后我们准备写代码了。从建立接口开始。
给项目添加一个叫IClient的类,代码如下:
财管家园 fs119.net PublicInterfaceIClient 财软,联盟,fs119.net
SubStart(ByValControllerAsController)
SubDisplay(ByValTextAsString)
SubFailed(ByValeAsException)
SubCompleted(ByValCancelledAsBoolean)
EndInterface
接着添加一个叫IWorker的类,代码如下:
财.管家园.fs119.net
财管 家园 fs119.net PublicInterfaceIWorker
SubInitialize(ByValControllerAsIController)
SubStart()
EndInterface
最后使用下面的代码添加一个叫IController的类:
财管家园,fs119.net
财管家园.fs119.net
PublicInterfaceIController
ReadOnlyPropertyRunning()AsBoolean
SubDisplay(ByValTextAsString)
SubSetPercent(ByValPercentAsInteger)
SubFailed(ByValeAsException)
SubCompleted(ByValCancelledAsBoolean)
EndInterface
这时我们已经定义了先前讨论过的类图中的所有接口。因此,现在我们可以实现Controller类。 财管家,园,fs119.net
现在我们将实现框架的核心部分--Controller类。该类将包含启动工作线程的代码并在工作线程完成前,作为UI线程和工作线程的中介。
给项目添加一个叫Controller的新类。首先我们将添加一个Imports并声明一些变量:
财软联盟,fs119.net
财软联盟,fs119.net
财管家园,fs119.net ImportsSystem.Threading
PublicClassController
ImplementsIController
PrivatemWorkerAsIWorker
PrivatemClientAsForm
PrivatemRunningAsBoolean
PrivatemPercentAsInteger
接着我们需要定义一些委托(delegate)。委托是指向方法的形式指针,并且某个方法的委托必须与该方法的特征(参数类型等)相同。
财管家园 fs119.net
财 管家园 fs119.net
财.软联盟.fs119.net 在很多情况中使用委托。在本例中,它们非常重要,因为它们允许一个线程能调用窗体的方法,因此它运行在窗体的UI线程中。IClient所定义的三个窗体的方法都需要委托:
财软.联盟.fs119.net
财管家.园.fs119.net
'本委托的特征与IClient.Completed匹配,用于安全地调用UI线程上的法
PrivateDelegateSubCompletedDelegate(ByValCancelledAsBoolean)
'本委托的特征与IClient.Display匹配,用于安全地调用UI线程上的法
PrivateDelegateSubDisplayDelegate(ByValTextAsString)
'本委托的特征与IClient.Failed匹配,用于安全地调用UI线程上的法
PrivateDelegateSubFailedDelegate(ByValeAsException)
IClient也定义了Start方法,但我们将从UI线程自身中调用它,因此不需要委托。
下一步写将被UI线程调用的代码。该代码包含constructor、Start、Cancel方法和Percent属性。我将这些写入一个区域(Region),可以使它们在UI线程中被调用时比较清晰。
财 软联盟 fs119.net
财管.家园.fs119.net 财管.家园.fs119.net
#Region"CodecalledfromUIthread"
'使用客户(client)初始化controller
PublicSubNew(ByValClientAsIClient)
mClient=CType(Client,Form)
EndSub
'本方法被UI调用并在UI线程上运行。它启动工作线程
PublicSubStart(OptionalByValWorkerAsIWorker=Nothing)
'如果已经运行则产生一个错误信息
IfmRunningThen
ThrowNewException("Backgroundprocessalreadyrunning")
EndIf
mRunning=True
'保存worker对象的指针并初始化该对象,因此它有一个指向Controller的指针
mWorker=Worker
mWorker.Initialize(Me)
'创建后台线程作后台处理
DimbackThreadAsNewThread(AddressOfmWorker.Start)
'开始后台工作
backThread.Start()
'告诉客户端后台工作开始了
CType(mClient,IClient).Start(Me)
EndSub
'本方法被UI调用并在UI线程上运行。它仅仅设置一个请求"取消"的标记
PublicSubCancel()
mRunning=False
EndSub
'返回完成的百分比值,只被UI线程调用
PublicReadOnlyPropertyPercent()AsInteger
Get
ReturnmPercent
EndGet
EndProperty
#EndRegion
唯一特别的代码是在Start方法中我们建立了工作线程然后启动它。
财,软联盟,fs119.net
DimbackThreadAsNewThread(AddressOfmWorker.Start)
backThread.Start()
为了建立该线程,我们传递了Worker对象的IWorker的接口的Start方法的地址。接着我们简单地调用了线程对象的Start方法开始该处理。在这儿我们必须仔细,保证既没有UI与Worker直接交互,也没有Worker与UI直接交互。
注意"取消"方法只是设置了一个标志用于显示我们想工作不再运行。直到工作代码周期性查看是否该停止运行。
现在我们实现在Worker对象运行时将被工作线程调用的代码。该代码有趣一些,它将工作线程调用的Display和Completed方法传递到UI,但却在UI线程上。
为了实现它,我们使用窗体对象的Invoke方法。Invoke方法接受指向该窗体将调用的方法的委托,同时有一个含有该方法的参数的类型对象数组。
Invoke方法不能直接调用窗体的方法。它请求窗体转向并使用该窗体的UI线程来调用方法。这通过发送一个Windows消息给窗体在后台实现。这意味着窗体获取这些方法调用与它从操作系统本身获取click或keypress事件非常相象。
财软.联盟.fs119.net
财软.联盟.fs119.net
财管家园,fs119.net
财,管家园,fs119.net
财管,家园,fs119.net
典型情况下这些并不麻烦。其结果是Invoke方法触发一个进程,通过该进程窗体终止在自己的UI线程上运行的方法,这正是我们的设计目标。
同样这些代码写入一个区域(Region),可以使它们在worker线程中被调用时比较清晰。
财软联 盟 fs119.net
财软联盟.fs119.net 财软联盟.fs119.net
#Region"Codecalledfromtheworkerthread"
'从worker线程调用来更新显示
'它用状态信息触发了一个向UI的方法调用
'该调用在UI线程上作出
PrivateSubDisplay(ByValTextAsString)_
ImplementsIController.Display
DimdispAsNewDisplayDelegate(_
AddressOfCType(mClient,IClient).Display)
Dimar()AsObject={Text}
'调用UI线程的客户窗体来更新显示
mClient.BeginInvoke(disp,ar)
EndSub
'从worker线程调用用于显示失败。
'它用exception对象触发了一个向UI的方法调用
'该调用在UI线程上作出
PrivateSubFailed(ByValeAsException)_
ImplementsIController.Failed
DimdispAsNewFailedDelegate(_
AddressOfCType(mClient,IClient).Failed)
Dimar()AsObject={e}
'调用UI线程上的客户窗体来显示失败
mClient.Invoke(disp,ar)
EndSub
'从worker线程调用来表明完成百分比
'该值进入Controller,如果需要能在那儿被UI读取
PrivateSubSetPercent(ByValPercentAsInteger)_
ImplementsIController.SetPercent
mPercent=Percent
EndSub
'从worker线程调用来显示已经完成
'传递了一个参数用于显示真的完成了和者"取消"了
'该调用在UI线程中作出
PrivateSubCompleted(ByValCancelledAsBoolean)_
ImplementsIController.Completed
mRunning=False
DimcompAsNewCompletedDelegate(_
AddressOfCType(mClient,IClient).Completed)
Dimar()AsObject={Cancelled}
'从UI线程调用客户窗体来显示完成了
mClient.Invoke(comp,ar)
EndSub
'显示是否仍在运行或者有"取消"请求
'该调用在worker线程上作出,这样worker代码可以看是否应该温和地退出
PrivateReadOnlyPropertyRunning()AsBoolean_
ImplementsIController.Running
Get
ReturnmRunning
EndGet
EndProperty
#EndRegion
Failed和Completed方法使用了窗体的Invoke方法。下面使Failed的代码:
DimdispAsNewFailedDelegate(_
AddressOfCType(mClient,IClient).Failed)
Dimar()AsObject={e}
'从UI线程调用客户窗体来显示失败
mClient.Invoke(disp,ar)
首先所我们从IClient接口中建立一个委托指向窗体的Failed事件,接着我们声明了一个类型对象数组存放传递给该方法的参数值,最后调用客户窗体的Invoke方法,传递委托指针和参数数组给窗体。
财管 家园 fs119.net
随后窗体在UI线程中使用这些参数调用该方法,它可以安全的运行来更新显示。
这整个过程是同步的,意味着向窗体作调用时工作线程被阻塞。虽然因为错误消息或者完成消息而阻塞工作线程是合适的,但我们不想因为一点点状态显示而阻塞它。
为了避免在状态显示时的阻塞,Display方法用BeginInvoke代替了Invoke.BeginInvoke促成窗体上的方法调用异步完成,这样工作线程能保持运行而不需要等待窗体显示方法的完成。
财软联盟 fs119.net
财,软联盟,fs119.net DimdispAsNewDisplayDelegate(_
AddressOfCType(mClient,IClient).Display)
Dimar()AsObject={Text}
'调用UI线程上的客户窗体来更新显示
mClient.BeginInvoke(disp,ar)
在这种情况下使用BeginInvoke通过避免阻塞保持了工作线程尽可能地高效率运行。 财软,联盟,fs119.net
财软,联盟,fs119.net
财软联盟.fs119.net
财软联盟,fs119.net
财,管家园,fs119.net
最后我们建立ActivityBar控件来显示动态点。
给项目添加一个叫ActivityBar的用户控件,将该控件调整到大约宽110,高20,可以拖拉边界或在属性窗口中设定Size值来实现。
其它的在代码中实现。为了建立一系列动态闪烁的"亮点",我们将Timer控件与一组PictureBox控件一起使用。每次Timer控件到期,我们将下一个PictureBox设为绿色,将已经是绿色的变成窗体色。
放置Timer控件,将它的名字改为tmAnim,Interval属性设为300。
另外,在Components页上有一个不同的Timer控件。这是个多线程时钟。换句话说,它在后台线程上建立Elapsed事件,不同于UI线程上的Windows窗体时钟。在建立UI时这明显达不到目标,因为Elapsed事件中的代码明显不能直接与UI交互。
现在向控件中添加下列代码: 财管家园.fs119.net
财 软联盟 fs119.net
财管家,园,fs119.net PrivatemBoxesAsNewArrayList()
PrivatemCountAsInteger
PrivateSubActivityBar_Load(ByValsenderAsSystem.Object,_
ByValeAsSystem.EventArgs)HandlesMyBase.Load
DimindexAsInteger
IfmBoxes.Count=0Then
Forindex=0To6
mBoxes.Add(CreateBox(index))
Next
EndIf
mCount=0
EndSub
PrivateFunctionCreateBox(ByValindexAsInteger)AsPictureBox
DimboxAsNewPictureBox()
Withbox
SetPosition(box,index)
.BorderStyle=BorderStyle.Fixed3D
.Parent=Me
.Visible=True
EndWith
Returnbox
EndFunction
PrivateSubGrayDisplay()
DimindexAsInteger
Forindex=0To6
CType(mBoxes(index),PictureBox).BackColor=Me.BackColor
Next
EndSub
PrivateSubSetPosition(ByValBoxAsPictureBox,ByValIndexAsInteger)
DimleftAsInteger=CInt(Me.Width/2-7*14/2)
DimtopAsInteger=CInt(Me.Height/2-5)
WithBox
财 软联盟 fs119.net
.Height=10
.Width=10
.Top=top
.Left=leftIndex*14
EndWith
EndSub
PrivateSubtmAnim_Tick(ByValsenderAsSystem.Object,_
ByValeAsSystem.EventArgs)HandlestmAnim.Tick
CType(mBoxes((mCount1)Mod7),PictureBox).BackColor=_
Color.LightGreen
CType(mBoxes(mCountMod7),PictureBox).BackColor=Me.BackColor
mCount=1
IfmCount>6ThenmCount=0
EndSub
PublicSubStart()
CType(mBoxes(0),PictureBox).BackColor=Color.LightGreen
tmAnim.Enabled=True
EndSub
PublicSub[Stop]()
tmAnim.Enabled=False
GrayDisplay()
EndSub
PrivateSubActivityBar_Resize(ByValsenderAsObject,_
ByValeAsSystem.EventArgs)HandlesMyBase.Resize
DimindexAsInteger
Forindex=0TomBoxes.Count-1
SetPosition(CType(mBoxes(index),PictureBox),index)
Next
EndSub
窗体的Load事件建立PictureBox控件并把它们放入一个数组,这样容易循环。Timer控件的Tick事件按次序循环使每个点变为绿色。
这些都由Start事件启动,由Stop事件停止。由于Stop是保留字,该方法的名称加上了方括号:[Stop]。Stop方法不仅停止定时器,而且使所有的方框变为灰色以显示没有当前活动。 财软联盟 fs119.net
前面我们看到了一个简单的Worker类。我们已经定义IWorker接口,现在能利用已经建立的Controller来增强该类。
首先建立Background.dll文件。这一步很重要,如果没有的话,我们在建立测试窗体时,ActivityBar控件不会在Toolbox上出现。
给解决方案添加一个叫bgTest的Windows窗体应用程序项目,将它设置为启动项目。
接着使用AddReferences对话框的Projects页来添加对Background项目的引用。
财.软联盟.fs119.net
财软联盟.fs119.net
财,管家园,fs119.net ImportsBackground
PublicClassWorkerImplementsIWorker
PrivatemControllerAsIController
PrivatemInnerAsInteger
PrivatemOuterAsInteger
PublicSubNew(ByValInnerSizeAsInteger,ByValOuterSizeAsInteger)
mInner=InnerSize
mOuter=OuterSize
EndSub
'被controller调用,这样可以得到一个controller的指针。
PrivateSubInit(ByValControllerAsIController)_
ImplementsIWorker.Initialize
mController=Controller
EndSub
PrivateSubWork()
ImplementsIWorker.Start
DiminnerIndexAsInteger
DimouterIndexAsInteger
DimvalueAsDouble
Try
ForouterIndex=0TomOuter
IfmController.RunningThen
mController.Display("Outerloop"&outerIndex&"starting")mController.SetPercent(CInt(outerIndex/mOuter*100))
Else
'有"取消"请求
mController.Completed(True)
ExitSub
EndIf
ForinnerIndex=0TomInner
'在此处作一些cool运算
value=Math.Sqrt(CDbl(innerIndex-outerIndex))
Next
Next
mController.SetPercent(100)
mController.Completed(False)
CatcheAsExceptionmController.Failed(e)EndTry
EndSub
EndClass
我们添加了Init方法来执行IWorker.Initialize。Controller调用该方法,这样就有了一个Controller对象的指针。
我们将Work方法改为私有(Private),仅仅用于执行IWorker.Start方法。该方法将在工作线程上运行。
Work方法使用Try..Catch块得到了加强,这样我们能捕捉任何错误并使用Controller的Failed方法将错误返回到UI。
假定代码能运行,在该代码运行时,我们调用Controller对象的Display和SetPercent方法来更新状态和完成百分比。
我们也周期性地检查Controller对象的Running属性来查看是否有"取消"请求。如果有,就停止处理并显示由于有"取消"请求而完成。 财软联.盟.fs119.net
财.管家园.fs119.net
最后建立一个窗体来启动和取消后台处理。它也显示活动和状态信息。
给窗体添加两个按钮(btnStart和btnRequestCancel)、两个label(Label1和Label2)、一个ProgressBar(ProgressBar1)和ActivityBar(ActivityBar1),如图7所示:
图7:Form1的布局
窗体需要执行IClient,这样Controller控件才能与它交互。
ImportsBackgroundPublicClassForm1InheritsSystem.Windows.Forms.FormImplementsIClient窗体也需要一个Controller对象和一个标志来追踪后台处理是活动的或完成了。
PrivatemControllerAsNewController(Me)PrivatemActiveAsBoolean
财软 联盟 fs119.net
财软联盟,fs119.net
财软联盟.fs119.net #Region"IClient"
PrivateSubTaskStarted(ByValControllerAsController)_
ImplementsIClient.Start
mActive=True
Label1.Text="Starting"
Label2.Text="0%"
ProgressBar1.Value=0
ActivityBar1.Start()
EndSub
PrivateSubTaskStatus(ByValTextAsString)_
ImplementsIClient.Display
Label1.Text=Text
Label2.Text=CStr(mController.Percent)&"%"
ProgressBar1.Value=mController.Percent
EndSub
PrivateSubTaskFailed(ByValeAsException)_
ImplementsIClient.Failed
ActivityBar1.Stop()
Label1.Text=e.Message
MsgBox(e.ToString)
mActive=False
EndSub
PrivateSubTaskCompleted(ByValCancelledAsBoolean)_
ImplementsIClient.Completed
Label1.Text="Completed"
Label2.Text=CStr(mController.Percent)&"%"
ProgressBar1.Value=mController.Percent
ActivityBar1.Stop()
mActive=False
EndSub
#EndRegion
注意所有的代码都没有处理线程。每部分都包含监视后台处理的状态时可以作适当的响应的代码。每次我们都更新过程状态信息的显示、它的完成百分比(在文本框和ProgressBar中)并启动和停止ActivityBar控件。
标志mActive很重要。当工作线程活动时,如果用户关闭了窗体,程序可能挂起或者不稳定。为了避免这种情况,如果后台处理是激活的,我们截取窗体的Closing事件并在后台处理活动时终止关闭的企图。
财管 家园 fs119.net
财软联盟,fs119.net PrivateSubForm1_Closing(ByValsenderAsObject,_
ByValeAsSystem.ComponentModel.CancelEventArgs)_
Handles
MyBase.Closing
e.Cancel=mActive
EndSub
在本例中我们选择初始化"取消"操作,这依赖于具体的应用程序需求。
下面的代码用于实现按钮的Click事件:
财管家 园 fs119.net
财.管家园.fs119.net PrivateSubbtnStart_Click(ByValsenderAsSystem.Object,_
ByValeAsSystem.EventArgs)Handles
btnStart.Click
mController.Start(NewWorker(2000000,100))
EndSub
PrivateSubbtnStop_Click(ByValsenderAsSystem.Object,_
ByValeAsSystem.EventArgs)HandlesbtnStop.Click
Label1.Text="Cancelling..."
mController.Cancel()
EndSubStart
按钮简单地调用Controller的Start方法,将Worker对象的一个实例传递给它。
为了使它运行得更有趣,你也许要调整初始化Worker对象得值。本文中的值在两个P3/450得计算机上运行很好。实际的Worker对象执行更有意思的工作,但也是长时间的处理。
Cancel按钮调用Controller对象Cancel方法,并更新显示来表明有"取消"请求。这仅仅是个"取消"请求,在工作实际停止前也许有一段时间。比较好的方法是给用户一些回应,至少表明点击按钮已经被注意到了。
运行该程序。Start按钮按下时,Worker启动了,随着它的运行显示在发生变化。你可以在屏幕上移动窗体并与窗体交互,因为UI线程本质上是空闲的,准备好了与你交互。
同时,工作线程正在后台处理繁忙工作,给UI线程发送周期性的状态更新消息以供显示。
多线程是个非常强大的工具,在任何需要长时间运行的事务中都能使用。我们可以用它运行工作代码而不停止用户界面。同时,多线程的使用可能变得难以想象的复杂,更加难于调试。
但这不总是可行的,我们应该力争给每个工作线程提供一组该线程操作的独立数据。最简单的实现方法是为每个线程创建一个对象,该对象包含线程需要操作的数据和工作的代码。
通过实现一个结构化的框架作为工作线程与UI线程的中介,我们难以想像地简化了编写多线程工作代码和控制它的UI代码。本文中我有一个框架实例,你能改变它使之适应你的应用程序需求。
财管家.园.fs119.net
财管家园,fs119.net
财管家园,fs119.net
Google.cn搜索相关文章:
谷歌中搜索全球网 VisualBasic.NET实现后台处理
百度中搜索 VisualBasic.NET实现后台处理
谷歌中搜索www.fs119.net VisualBasic.NET实现后台处理
下一篇:VisualBasic.NET快速开发MIS系统
精品课程推荐
- 在VisualBasic.NET中实现后台进程(三
- 在VisualBasic.NET中实现后台进程(二
- 在VisualBasic.NET中实现后台进程(一
- VB.NET开发互联网应用
- VisualBasic.NET中操作MsAgent
- VisualBasic.NET快速开发MIS系统
- VisualBasic.NET实现后台处理
- 运用VB.net创建Web服务访问程序
- 一步一步创建VisualBasic.NET控件
- 如何用VB.Net创建一个三层的数据库应
- VisualBasic.NET中动态加载类
- VisualBasic.NET中访问数据的方法
- 消息队列在VB.NET数据库开发中的应用
- VB.NET窗体操作技巧两则
- VB.NET中的多线程开发