科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件Visual Basic .NET实现后台处理(下 )

Visual Basic .NET实现后台处理(下 )

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

我们将在一个类库项目中实现框架,这样就可以在任何需要执行后台处理的应用程序中使用它。

作者:陶刚编译 来源:yesky 2007年11月10日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
 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

  接着我们需要定义一些委托(delegate)。委托是指向方法的形式指针,并且某个方法的委托必须与该方法的特征(参数类型等)相同。

  在很多情况中使用委托。在本例中,它们非常重要,因为它们允许一个线程能调用窗体的方法,因此它运行在窗体的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 " Code called from UI thread "

' 使用客户(client)初始化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

' 保存worker对象的指针并初始化该对象,因此它有一个指向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与UI直接交互。

  注意"取消"方法只是设置了一个标志用于显示我们想工作不再运行。直到工作代码周期性查看是否该停止运行。

  现在我们实现在Worker对象运行时将被工作线程调用的代码。该代码有趣一些,它将工作线程调用的Display和Completed方法传递到UI,但却在UI线程上。

  为了实现它,我们使用窗体对象的Invoke方法。Invoke方法接受指向该窗体将调用的方法的委托,同时有一个含有该方法的参数的类型对象数组。

  Invoke方法不能直接调用窗体的方法。它请求窗体转向并使用该窗体的UI线程来调用方法。这通过发送一个Windows消息给窗体在后台实现。这意味着窗体获取这些方法调用与它从操作系统本身获取click或 keypress事件非常相象。

  典型情况下这些并不麻烦。其结果是Invoke方法触发一个进程,通过该进程窗体终止在自己的UI线程上运行的方法,这正是我们的设计目标。

  同样这些代码写入一个区域(Region),可以使它们在worker线程中被调用时比较清晰。

#Region " Code called from the worker thread "

'从worker线程调用来更新显示

'它用状态信息触发了一个向UI的方法调用

'该调用在UI线程上作出

Private Sub Display(ByVal Text As String) _

Implements IController.Display

Dim disp As New DisplayDelegate( _

AddressOf CType(mClient, IClient).Display)

Dim ar() As Object = {Text}

'调用UI线程的客户窗体来更新显示

mClient.BeginInvoke(disp, ar)

End Sub

'从worker线程调用用于显示失败。

'它用exception对象触发了一个向UI的方法调用

'该调用在UI线程上作出

Private Sub Failed(ByVal e As Exception) _

Implements IController.Failed


Dim disp As New FailedDelegate(_

AddressOf CType(mClient, IClient).Failed)

Dim ar() As Object = {e}

'调用UI线程上的客户窗体来显示失败

mClient.Invoke(disp, ar)

End Sub

'从worker线程调用来表明完成百分比

'该值进入Controller,如果需要能在那儿被UI读取

Private Sub SetPercent(ByVal Percent As Integer) _

Implements IController.SetPercent

mPercent = Percent

End Sub

'从worker线程调用来显示已经完成

'传递了一个参数用于显示真的完成了和者"取消"了

'该调用在UI线程中作出

Private Sub Completed(ByVal Cancelled As Boolean) _

Implements IController.Completed

mRunning = False

Dim comp As New CompletedDelegate( _

AddressOf CType(mClient, IClient).Completed)

Dim ar() As Object = {Cancelled}

'从UI线程调用客户窗体来显示完成了

mClient.Invoke(comp, ar)

End Sub

'显示是否仍在运行或者有"取消"请求

'该调用在worker线程上作出,这样worker代码可以看是否应该温和地退出

Private ReadOnly Property Running() As Boolean _

Implements IController.Running

Get

Return mRunning

End Get

End Property

#End Region

Failed和Completed方法使用了窗体的Invoke方法。下面使Failed的代码:

Dim disp As New FailedDelegate(_

AddressOf CType(mClient, IClient).Failed)

Dim ar() As Object = {e}

'从UI线程调用客户窗体来显示失败

mClient.Invoke(disp, ar)

  首先所我们从IClient接口中建立一个委托指向窗体的Failed事件,接着我们声明了一个类型对象数组存放传递给该方法的参数值,最后调用客户窗体的Invoke方法,传递委托指针和参数数组给窗体。

  随后窗体在UI线程中使用这些参数调用该方法,它可以安全的运行来更新显示。

  这整个过程是同步的,意味着向窗体作调用时工作线程被阻塞。虽然因为错误消息或者完成消息而阻塞工作线程是合适的,但我们不想因为一点点状态显示而阻塞它。

  为了避免在状态显示时的阻塞,Display方法用BeginInvoke代替了Invoke. BeginInvoke促成窗体上的方法调用异步完成,这样工作线程能保持运行而不需要等待窗体显示方法的完成。

Dim disp As New DisplayDelegate( _

AddressOf CType(mClient, IClient).Display)

Dim ar() As Object = {Text}

'调用UI线程上的客户窗体来更新显示

mClient.BeginInvoke(disp, ar)

  在这种情况下使用BeginInvoke通过避免阻塞保持了工作线程尽可能地高效率运行。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章