在 Visual Basic .NET 中实现后台进程(二)
架构设计
要实现我们讨论的行为,显然需要实现 Controller 类。为了使此架构能够在多数方案中应用,我们还会定义一些正式接口,可以由 Controller 在与 UI(或客户端)和辅助线程交互时使用。
通过为客户端和辅助线程定义正式接口,我们可以在不同的情况下使用相同的 Controller 对象,还可以根据需要使用不同的 UI 要求和不同的 Worker 对象。
下面的 UML 类图表显示了 Controller 类以及 IClient 和 IWorker 接口。它还显示了 IController 接口,辅助代码将通过它与 Controller 对象交互。
图 5:Controller 和相关接口的类图表
IClient 接口定义的方法将由 Controller 对象调用,用于向客户端 UI 通报 Worker 的开始时间、结束时间和任何中间状态消息。它还包含一个指示辅助代码失败的方法。
多数情况下,我们可以将这些方法作为由 Controller 对象发出而由 UI 处理的事件来实现。但是,从辅助线程发出事件然后由 UI 线程正确处理并非易事,因而我们将其作为一组方法来进行实现。
使控制器代码(在辅助代码上运行)调用 UI 中的这些方法并由 UI 线程进行处理,这样相对要简单得多。
同样,IWorker 接口定义了由 Controller 对象调用的、使其可以与辅助代码交互的方法。使用 Initialize 方法可以为辅助代码提供对 Controller 对象的引用,而使用 Start 方法可以启动后台线程上的操作。
由于线程的工作方式,Start 方法无法包含任何参数。启动新线程时,必须将不接受任何参数的方法的地址传递给线程。
请注意,IWorker 接口中不存在 Cancel 或 Stop 方法。我们不能强制辅助代码停止,同时也没有这个必要;但是辅助代码可以使用 IController 接口询问 Controller 对象是否存在取消请求。
IController 接口定义了辅助代码可以在 Controller 对象上调用的方法。它允许辅助代码检查 Running 标志。如果存在取消请求,Running 标志即为 False。它还允许辅助代码在工作完成或无法完成时告诉 Controller,并允许使用状态消息和完成百分比值(0 到 100 之间的 Integer)更新 Controller。
最后我们定义了 Controller 对象。该对象中包含一些可以被 UI 代码调用的方法。其中包括 Start 方法,该方法可以通过为 Controller 对象提供对 Worker 对象的引用来启动后台操作。还包括 Cancel 方法,该方法用于请求取消操作。UI 也可以检查 Running 属性,查看是否存在取消请求;还可以检查 Percent 属性,查看任务完成的百分比。
Controller 类中包含的 constructor 方法接受 IClient 作为参数,还允许 UI 为 Controller 提供对窗体(用于处理 Worker 中的显示消息)的引用。
为了实现一系列动画点来显示线程的活动,我们将创建一个简单 Windows 窗体控件,该控件使用计时器以更改一系列 PictureBox 控件中的颜色。
实现方案
我们将在 Class Library(类库)项目中实现此架构,使其可用于需要运行后台进程的应用程序。
打开 Visual Studio .NET,然后创建一个名为 Background 的新 Class Library(类库)应用程序。由于此库将包含 Windows 窗体控件和窗体,因此需要使用 Add References(添加引用)对话框引用 System.Windows.Forms.dll 和 System.Windows.Drawing.dll。此外,我们可以使用项目的属性对话框在这些项目范围内导入命名空间,如图 6 所示。
图 6:使用项目属性添加项目范围内的命名空间 Imports
此操作完成后,就可以开始编码了。让我们先从创建接口开始。
定义接口
在名为 IClient 的项目中添加一个类,并用以下代码替换其代码:
Public Inte易做图ce IClient Sub Start(ByVal Controller As Controller) Sub Display(ByVal Text As String) Sub Failed(ByVal e As Exception) Sub Completed(ByVal Cancelled As Boolean) End Inte易做图ce
然后添加名为 IWorker 的类,并用以下代码替换其代码:
Public Inte易做图ce IWorker Sub Initialize(ByVal Controller As IController) Sub Start() End Inte易做图ce
最后添加名为 IController 的类,代码如下:
Public Inte易做图ce IController ReadOnly Property Running() As Boolean Sub Display(ByVal Text As String) Sub SetPercent(ByVal Percent As Integer) Sub Failed(ByVal e As Exception) Sub Completed(ByVal Cancelled As Boolean) End Inte易做图ce
至此,我们已定义了本文前面所述的所有类图表中的接口。现在可以实现 Controller 类了。
Controller 类
现在,我们可以实现架构的核心,Controller 类。此类中包含的代码可用于启动辅助线程,以及在辅助线程完成之前充当 UI 线程和辅助线程之间的媒介。
在名为 Controller 的项目中添加一个新类。首先添加 Imports,并声明一些变量:
Imports System.Threading Public Class Controller Implements IController Private mWorker As IWorker Private mClient As Form Private mRunning As Boolean Private mPercent As Integer
然后需要声明一些委托。委托是方法的正式指针,而且方法的委托必须具有与方法本身相同的方法签名(参数类型等)。
委托的用途很广。在我们的示例中,委托非常重要,因为委托使我们可以让一个线程调用窗体上的方法,使其在该窗体的 UI 线程上运行。正如 IClient 所定义的那样,要在窗体上调用的三个方法都需要委托:
此委托签名与 IClient.Completed 中的签名相匹配,并用于安全地 调用 UI 线程上的方法 Private Delegate Sub CompletedDelegate(ByVal Cancelled As Boolean) 此委托签名与 IClient.Display 中的签名相匹配,并用于安全地 调用 UI 线程上的方法 Private Delegate Sub DisplayDelegate(ByVal Text As String) 此委托签名与 IClient.Failed 中的签名相匹配,并用于安全地 调用 UI 线程上的方法 Private Delegate Sub FailedDelegate(ByVal e As Exception)
IClient 还定义了 Start 方法,但是该方法可以从 UI 线程调用,因此不需要委托。
下面编写将从 UI 线程调用的代码。代码中包括 constructor 方法、Start 和 Cancel 方法以及 Percent 属性。我将这些内容放入 Region 中,便于大家清楚地了解它们是从 UI 线程调用的。
#Region " 从 UI 线程调用的代码 " 使用客户端初始化 Controller Public Sub New(ByVal Client As IClient) mClient = CType(Client, Form) End Sub 此方法由 UI 调用,因此在 UI 线程上运行。此处我们将 启动辅助线程 Public Sub Start(Optional ByVal Worker As IWorker = Nothing) 如果辅助线程已经启动,将产生错误 If mRunning Then Throw New Exception("Background process already running") End If mRunning = True 存储对辅助对象的引用,并 初始化辅助对象,使其包含 对 Controller 的引用 mWorker = Worker mWorker.Initialize(Me) 创建后台线程 以进行后台操作 Dim backThread As New Thread(AddressOf mWorker.Start) 开始后台工作 backThread.Start() 告诉客户端后台工作已开始 CType(mClient, IClient).Start(Me) End Sub 此代码由 UI 调用,因此在 UI 线程上运行。它只设置了请求 取消的标志 Public Sub Cancel() mRunning = False End Sub 返回完成百分比值,并且 只被 UI 线程调用 Public ReadOnly Property Percent() As Integer Get Return mPercent End Get End Property #End Region
此处唯一比较特殊的代码位于 Start 方法中,我们可以在该方法中创建辅助线程然后启动该线程:
Dim backThread As New Thread(AddressOf mWorker.Start) backThread.Start()
要创建线程,需要在 Worker 对象的 IWorker 接口上传递 Start 方法的地址。然后,只需调用线程对象的 Start 方法即可开始操作。此时我们要特别注意,UI 不应直接与 Worker 交互,Worker
补充:Web开发 , ASP.Net ,