用户名: 密码: 验证码: gdcode 注册

在VisualBasic.NET中实现后台进程(二)

时间:2007-06-15 来源: 作者: 【字体: 减小 增大点击: 收藏 | 投稿
  
架构设计

要实现我们讨论的行为,显然需要实现Controller类。为了使此架构能够在多数方案中应用,我们还会定义一些正式接口,可以由Controller在与UI(或客户端)和辅助线程交互时使用。

财管家.园.fs119.net

通过为客户端和辅助线程定义正式接口,我们可以在不同的情况下使用相同的Controller对象,还可以根据需要使用不同的UI要求和不同的Worker对象。

财管.家园.fs119.net

下面的UML类图表显示了Controller类以及IClientIWorker接口。它还显示了IController接口,辅助代码将通过它与Controller对象交互。 财管家园 fs119.net

财 管家园 fs119.net

图5:Controller和相关接口的类图表
财,管家园,fs119.net

IClient接口定义的方法将由Controller对象调用,用于向客户端UI通报Worker的开始时间、结束时间和任何中间状态消息。它还包含一个指示辅助代码失败的方法。

财管家园,fs119.net

多数情况下,我们可以将这些方法作为由Controller对象发出而由UI处理的事件来实现。但是,从辅助线程发出事件然后由UI线程正确处理并非易事,因而我们将其作为一组方法来进行实现。 财,软联盟,fs119.net

使控制器代码(在辅助代码上运行)调用UI中的这些方法并由UI线程进行处理,这样相对要简单得多。

财.管家园.fs119.net

同样,IWorker接口定义了由Controller对象调用的、使其可以与辅助代码交互的方法。使用Initialize方法可以为辅助代码提供对Controller对象的引用,而使用Start方法可以启动后台线程上的操作。

财管家,园,fs119.net

由于线程的工作方式,Start方法无法包含任何参数。启动新线程时,必须将不接受任何参数的方法的地址传递给线程。 财软.联盟.fs119.net

请注意,IWorker接口中不存在CancelStop方法。我们不能强制辅助代码停止,同时也没有这个必要;但是辅助代码可以使用IController接口询问Controller对象是否存在取消请求。 财软,联盟,fs119.net

IController接口定义了辅助代码可以在Controller对象上调用的方法。它允许辅助代码检查Running标志。如果存在取消请求,Running标志即为False。它还允许辅助代码在工作完成或无法完成时告诉Controller,并允许使用状态消息和完成百分比值(0到100之间的Integer)更新Controller财管家园.fs119.net

最后我们定义了Controller对象。该对象中包含一些可以被UI代码调用的方法。其中包括Start方法,该方法可以通过为Controller对象提供对Worker对象的引用来启动后台操作。还包括Cancel方法,该方法用于请求取消操作。UI也可以检查Running属性,查看是否存在取消请求;还可以检查Percent属性,查看任务完成的百分比。 财管家园,fs119.net

Controller类中包含的constructor方法接受IClient作为参数,还允许UI为Controller提供对窗体(用于处理Worker中的显示消息)的引用。 财管家 园 fs119.net

为了实现一系列动画点来显示线程的活动,我们将创建一个简单Windows窗体控件,该控件使用计时器以更改一系列PictureBox控件中的颜色。 财 管家园 fs119.net

财管家,园,fs119.net

财软联盟.fs119.net


财软联.盟.fs119.net

财软联盟,fs119.net

财 软联盟 fs119.net

实现方案

我们将在ClassLibrary(类库)项目中实现此架构,使其可用于需要运行后台进程的应用程序。

财管家园.fs119.net

打开VisualStudio.NET,然后创建一个名为Background的新ClassLibrary(类库)应用程序。由于此库将包含Windows窗体控件和窗体,因此需要使用AddReferences(添加引用)对话框引用System.Windows.Forms.dllSystem.Windows.Drawing.dll。此外,我们可以使用项目的属性对话框在这些项目范围内导入命名空间,如图6所示。

财管家园,fs119.net

财管 家园 fs119.net

图6:使用项目属性添加项目范围内的命名空间Imports 财软联,盟,fs119.net

此操作完成后,就可以开始编码了。让我们先从创建接口开始。

财管 家园 fs119.net

定义接口

在名为IClient的项目中添加一个类,并用以下代码替换其代码:

财管家园,fs119.net

PublicInterfaceIClient SubStart(ByValControllerAsController) SubDisplay(ByValTextAsString) SubFailed(ByValeAsException) SubCompleted(ByValCancelledAsBoolean) EndInterface 财软联盟,fs119.net

然后添加名为IWorker的类,并用以下代码替换其代码: 财管 家园 fs119.net

PublicInterfaceIWorker SubInitialize(ByValControllerAsIController) SubStart() EndInterface
财软联盟 fs119.net

最后添加名为IController的类,代码如下:

财.管家园.fs119.net

PublicInterfaceIController ReadOnlyPropertyRunning()AsBoolean SubDisplay(ByValTextAsString) SubSetPercent(ByValPercentAsInteger) SubFailed(ByValeAsException) SubCompleted(ByValCancelledAsBoolean) EndInterface 财.软联盟.fs119.net

至此,我们已定义了本文前面所述的所有类图表中的接口。现在可以实现Controller类了。 财软联.盟.fs119.net

财管家园.fs119.net

财软联,盟,fs119.net


财软联盟,fs119.net

财软联盟,fs119.net

财,管家园,fs119.net

Controller类

现在,我们可以实现架构的核心,Controller类。此类中包含的代码可用于启动辅助线程,以及在辅助线程完成之前充当UI线程和辅助线程之间的媒介。

财管 家园 fs119.net

在名为Controller的项目中添加一个新类。首先添加Imports,并声明一些变量:

财管家园,fs119.net

ImportsSystem.Threading PublicClassController ImplementsIController PrivatemWorkerAsIWorker PrivatemClientAsForm PrivatemRunningAsBoolean PrivatemPercentAsInteger

财管家 园 fs119.net

然后需要声明一些委托。委托是方法的正式指针,而且方法的委托必须具有与方法本身相同的方法签名(参数类型等)。 财,管家园,fs119.net

委托的用途很广。在我们的示例中,委托非常重要,因为委托使我们可以让一个线程调用窗体上的方法,使其在该窗体的UI线程上运行。正如IClient所定义的那样,要在窗体上调用的三个方法都需要委托: 财管.家园.fs119.net

'此委托签名与IClient.Completed '中的签名相匹配,并用于安全地 '调用UI线程上的方法 PrivateDelegateSubCompletedDelegate(ByValCancelledAsBoolean) '此委托签名与IClient.Display '中的签名相匹配,并用于安全地 '调用UI线程上的方法 PrivateDelegateSubDisplayDelegate(ByValTextAsString) '此委托签名与IClient.Failed '中的签名相匹配,并用于安全地 '调用UI线程上的方法 PrivateDelegateSubFailedDelegate(ByValeAsException) 财软联盟,fs119.net

IClient还定义了Start方法,但是该方法可以从UI线程调用,因此不需要委托。

财软联.盟.fs119.net

下面编写将从UI线程调用的代码。代码中包括constructor方法、StartCancel方法以及Percent属性。我将这些内容放入Region中,便于大家清楚地了解它们是从UI线程调用的。

财软.联盟.fs119.net

#Region"从UI线程调用的代码" '使用客户端初始化Controller PublicSubNew(ByValClientAsIClient) mClient=CType(Client,Form) EndSub '此方法由UI调用,因此在 'UI线程上运行。此处我们将 '启动辅助线程 PublicSubStart(OptionalByValWorkerAsIWorker=Nothing) '如果辅助线程已经启动,将产生错误 IfmRunningThen ThrowNewException("Backgroundprocessalreadyrunning") EndIf mRunning=True '存储对辅助对象的引用,并 '初始化辅助对象,使其包含 '对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 财管家园 fs119.net

此处唯一比较特殊的代码位于Start方法中,我们可以在该方法中创建辅助线程然后启动该线程: 财软联 盟 fs119.net

DimbackThreadAsNewThread(AddressOfmWorker.Start) backThread.Start()
财管.家园.fs119.net

要创建线程,需要在Worker对象的IWorker接口上传递Start方法的地址。然后,只需调用线程对象的Start方法即可开始操作。此时我们要特别注意,UI不应直接与Worker交互,Worker也不应直接与UI交互。 财管家园 fs119.net

请注意,Cancel方法只设置一个标志,表明我们不希望继续运行。辅助代码应定期查看此标志,以确定是否应该停止运行。

财管家园.fs119.net

现在,我们可以实现Worker对象运行时将由辅助线程调用的代码。此代码比较有趣,因为它必须将DisplayCompleted从辅助线程中转至UI线程,同时还要在UI线程上完成此操作。 财管 家园 fs119.net

要完成此操作,我们可以使用Form对象的Invoke方法。此方法接受窗体应该调用的方法的委托指针,以及包含该方法的参数的Object类型数组。 财软联盟.fs119.net

Invoke方法不直接调用窗体上的方法,而是请求窗体返回并使用窗体的UI线程调用该方法。此操作可通过向窗体发送Windows消息在后台完成。这说明窗体获得这些方法调用的方式与从操作系统中获得clickkeypress事件的方式基本相同。 财软联盟,fs119.net

财管家,园,fs119.net

财管,家园,fs119.net


财.软联盟.fs119.net

财管 家园 fs119.net

财管家园.fs119.net

通常,这些细节不会影响大局。结果由Invoke方法触发一个进程,通过该进程窗体将终止其UI线程上运行的方法,这就是我们要实现的目标。 财软联 盟 fs119.net

再次重申,此代码位于Region内,目的是为了明确它将在辅助线程上调用:

财管家园.fs119.net

#Region"从辅助线程调用的代码" '从辅助线程调用,以更新显示 '这将触发对包含状态文本的UI的 '方法调用-该调用是在UI线程上 '进行的 PrivateSubDisplay(ByValTextAsString)_ ImplementsIController.Display DimdispAsNewDisplayDelegate(_ AddressOfCType(mClient,IClient).Display) Dimar()AsObject={Text} '调用UI线程上的客户端窗体 '以更新显示 mClient.BeginInvoke(disp,ar) EndSub '从辅助线程调用,以表明出现故障 '这将触发对包含异常对象的UI的 '方法调用-该调用是在UI线程上 '进行的 PrivateSubFailed(ByValeAsException)_ ImplementsIController.Failed DimdispAsNewFailedDelegate(_ AddressOfCType(mClient,IClient).Failed) Dimar()AsObject={e} '在UI线程上调用客户端窗体 '以表明出现故障 mClient.Invoke(disp,ar) EndSub '从辅助线程上调用,以指出完成的百分比 '值将转到Controller,由UI在需要时读取 PrivateSubSetPercent(ByValPercentAsInteger)_ ImplementsIController.SetPercent mPercent=Percent EndSub '从辅助线程调用,以表明已完成 '我们还传递参数,以表明是否真正完成, '以及是否取消在UI线程上进行的对UI '的调用 PrivateSubCompleted(ByValCancelledAsBoolean)_ ImplementsIController.Completed mRunning=False DimcompAsNewCompletedDelegate(_ AddressOfCType(mClient,IClient).Completed) Dimar()AsObject={Cancelled} '调用UI线程上的客户端窗体 '以表明已完成 mClient.Invoke(comp,ar) EndSub '表明是否仍在运行或是否已请求取消 '这将在辅助线程上进行调用,因此 '辅助代码可以查看它是否应该正常 '退出 PrivateReadOnlyPropertyRunning()AsBoolean_ ImplementsIController.Running Get ReturnmRunning EndGet EndProperty #EndRegion 财软联盟,fs119.net

FailedCompleted方法利用窗体的Invoke方法。例如,Failed方法可以执行以下操作: 财管家,园,fs119.net

DimdispAsNewFailedDelegate(_ AddressOfCType(mClient,IClient).Failed) Dimar()AsObject={e} '调用UI线程上的客户端窗体 '以表明出现故障 mClient.Invoke(disp,ar)

财软联,盟,fs119.net

首先创建一个委托,从IClient接口指向客户端窗体的Failed方法。然后声明包含向方法传递参数值的Object类型数组。最后调用客户端窗体的Invoke方法,将委托指针和参数数组传递给窗体。 财软.联盟.fs119.net

窗体将在UI线程(窗体在这里可以安全运行以更新显示)上使用这些参数调用此方法。

财管家 园 fs119.net

整个进程是同步进行的,即对窗体进行调用时辅助线程将停止。尽管可以在显示错误消息或完成消息时停止辅助线程,但我们并不希望显示每个小状态时都停止辅助线程。

财.管家园.fs119.net

财软联 盟 fs119.net

财 软联盟 fs119.net


财软联盟,fs119.net

财.管家园.fs119.net

财管.家园.fs119.net

为了避免显示状态时停止辅助线程,Display方法将使用BeginInvoke,而不使用InvokeBeginInvoke使窗体上的方法调用异步进行,这样辅助线程可以一直保持运行状态,不需要等待窗体上的显示方法完成:

财软 联盟 fs119.net

DimdispAsNewDisplayDelegate(_ AddressOfCType(mClient,IClient).Display) Dimar()AsObject={Text} '调用UI线程上的客户端窗体 '以更新显示 mClient.BeginInvoke(disp,ar)
财管家 园 fs119.net

以这种方式使用BeginInvoke可以防止辅助线程停止,使辅助线程具有尽可能高的性能。 财 管家园 fs119.net

ActivityBar控件

最后,我们来创建显示动画点的ActivityBar控件。

财管家园,fs119.net

在名为ActivityBar的项目中添加一个用户控件。 财 软联盟 fs119.net

将该控件的宽度调整为约110,高度调整为约20。可以通过拖动边界进行调整,也可以通过在Properties(属性)窗口中设置Size属性进行调整。

财管,家园,fs119.net

其余的操作将通过代码完成。要创建一系列在显示时不停闪烁的动画“灯”,可以使用带有Timer控件的一系列PictureBox控件。每次Timer控件关闭时,我们将使下一个PictureBox呈绿色显示,并将已经呈绿色显示的PictureBox更改为窗体的背景色。 财 软联盟 fs119.net

将WindowsForms(Windows窗体)选项卡中的Timer控件放入窗体中,然后将其名称更改为tmAnim。同时将Interval属性设置为300,以获得较好的动画速度。

财管家.园.fs119.net

顺便说一句,Components(组件)选项卡中有一个不同的Timer控件。它是一个多线程计时器。也就是说,该计时器将在后台线程中引发Elapsed事件,而不是象Windows窗体计时器那样在UI线程上引发Elapsed事件。建立UI时这种方法通常会产生相反的效果,因为Elapsed事件中的代码显然不能直接与我们的UI进行交互。

财软联,盟,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 .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
财 软联盟 fs119.net

窗体的Load事件创建PictureBox控件并将它们放入数组,这样便于我们在它们之间循环。Timer控件的Tick事件循环显示,使各个控件依次呈绿色。 财 管家园 fs119.net

所有操作由Start方法开始,由Stop事件结束。由于Stop是一个保留字,因此把这个方法名放在方括号内:[Stop]Stop方法不仅可以停止计时器,还可以灰显所有框,告诉用户这些框中当前没有活动。 财,管家园,fs119.net

财 软联盟 fs119.net

财.管家园.fs119.net


财管,家园,fs119.net

财软联 盟 fs119.net

财 软联盟 fs119.net

创建Worker类

本文前面已简单介绍了Worker类。因为我们已经定义了IWorker接口,所以可以增强该类,以利用我们创建的Controller财软联 盟 fs119.net

首先创建Background.dll文件。此步骤很重要,因为如果不完成此步骤,ActivityBar控件将无法在我们建立测试窗体时显示在工具箱上。 财管家 园 fs119.net

在解决方案中添加名为bgTest的WindowsFormsApplication(Windows窗体应用程序)。在SolutionExplorer(解决方案资源浏览器)中用右键单击该项目并选择相应的菜单项,将该程序设置为启动项目。

财管家.园.fs119.net

然后使用AddReferences(添加引用)对话框中的Projects(项目)选项卡,添加对Background项目的引用。

财管家园 fs119.net

现在,在名为Worker的项目中添加一个类。其中部分代码与前面所述的代码相同,但还包含一些不同的代码,用以实现IWorker接口(此处突出显示的部分):

财管 家园 fs119.net

ImportsBackground PublicClassWorker ImplementsIWorker 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 '此处进行一些有意思的计算 value=Math.Sqrt(CDbl(innerIndex-outerIndex)) Next Next mController.SetPercent(100) mController.Completed(False) CatcheAsException mController.Failed(e) EndTry EndSub EndClass
财管家园 fs119.net

 我们添加了能够实现IWorker.InitializeInit方法。Controller将调用此方法,因此以后我们可以引用Controller对象。 财软联盟.fs119.net

 我们还将Work方法更改为Private,只是为了实现IWorker.Start方法。此方法将在辅助线程上运行。 财软联盟,fs119.net

 我们增强了Work方法,使其可以使用Try..Catch块。这样我们可以使用Controller上的Failed方法捕捉任何错误并将其返回给UI。 财,管家园,fs119.net

 假设代码正在运行,我们调用Controller对象的DisplaySetPercent方法,使它们随着代码的运行更新其状态和完成的百分比。

财管家园,fs119.net

 我们还定期检查Controller对象的Running属性,查看是否存在取消请求。如果存在取消请求,则停止进程,并指示由于取消请求而停止操作 财软 联盟 fs119.net

财软联 盟 fs119.net

财.软联盟.fs119.net


文章摘自网络,如有侵权,请与我们联系.
数据统计中!!
上一篇:在VisualBasic.NET中实现后台进程(一)
下一篇:在VisualBasic.NET中实现后台进程(三)

精品课程推荐



用户名: 密码: 匿名? 注册