在你的VB.NET应用程序中使用多线程

** 在你的 VB.NET ** ** 应用程序中使用多线程 **

很长时间以来,开发人员一直要求微软为 VB 增加更多的线程功能——这一点在 VB.NET 中终于实现了。 VB6 不支持创建多线程的 EXE 、 DLL 以及 OCX 。但这种措词容易引起误解,这是因为 VB6 支持执行多个单线程的单元。一个单元实际上是代码执行的场所而且单元的边界限制了外部代码对单元内部的访问。

VB.NET 支持创建自由线程的应用程序。这意味着多个线程可以访问同一个共享的数据集。本文将带领你了解多线程的基本内容。

虽然 VB 支持多个单线程的单元,但并不支持允许多个线程在同一个数据集上运行的自由线程模型。在很多情况下,产生一个运行后台处理程序的新线程会提高应用程序的可用性。一种很显然的情况就是当执行一个可能使窗体看起来停止响应的长过程时,你一定会想在窗体上放置一个取消按钮。

** 解决方法 **

由于 VB.NET 使用公共语言运行时 (Common Language Runtime) ,它增强了很多新的特性,其中之一便是创建自由线程应用程序的能力。

在 VB.NET 中,开始使利用线程进行工作是很容易的。稍后我们会探究一些精妙之处,我们先创建一个简单的窗体,它生成一个执行后台处理程序的新线程。我们需要做的第一件事是将要在新线程上运行的后台处理程序。下面的代码执行一个相当长的运行过程——一个无限循环:

Private Sub BackgroundProcess()

Dim i As Integer = 1

Do While True

ListBox1.Items.Add("Iterations: " + i)

i += 1

Loop

End Sub

这段代码无限地循环并在每次循环中向窗体上的列表框中增加一个条目。如果你对 VB.NET 不熟悉的话,便会发现这段代码中有一些在 VB6 中无法完成的事:

l 在声明变量时对其赋值 Dim i As Integer=1

l 使用 += 操作符 i+=1 代替了 i=i+1

l Call 关键字已经被去除了

一旦我们有了一个工作过程,便需要将这段代码指派给一个新的线程并开始它的执行。完成这项工作,我们需要使用 Thread 对象,它是 .NET 框架类中 System.Threading 命名空间的一部分。当实例化了一个新的 Thread 类时,我们向其传递一个引用,这个引用指向我们想要在 Thread 类的构造函数中执行的代码块。下面的代码创建一个新的 Thread 对象并将指向 BackgroundProcess 的引用传递给它:

Dim t As Thread

t = New Thread(AddressOf Me.BackgroundProcess)

t.Start()

AddressOf 操作符为 BackgroundProcess 方法创建了一个委派对象。委派在 VB.NET 中是一种类型安全的、面向对象的函数指针。在线程被实例化之后,你可以通过调用线程的 Start() 方法开始执行代码。

** 使线程处于控制之下 **

当线程开始之后,你可以通过使用 Thread 对象的方法对其状态进行一定的控制。你可以通过调用 Thread.Sleep 方法暂停线程的执行。这个方法接收一个表示线程将要休眠多长时间的整型数值。如果在上例中你想要减缓列表框条目的添加,在代码中放置一个对此方法的调用:

Private Sub BackgroundProcess()

Dim i As Integer = 1

Do While True

ListBox1.Items.Add("Iterations: " + i)

i += 1

Thread.CurrentThread.Sleep(2000)

Loop

End Sub

CurrentThread 是一个公共静态属性,它可以使你获取一个对当前运行线程的引用。

你还可以通过调用 Thread.Sleep (System.Threading.Timeout.Infinite) 使一个线程处于一种时间不确定的休眠状态。要中断这种休眠,可以调用 Thread.Interrupt 方法。

类似与 Sleep 和 Interrupt 的是 Suspend 和 Resume 。 Suspend 允许你阻塞一个线程直到另外的线程调用 Thread.Resume 。 Sleep 和 Suspend 之间的区别在于后者不是立即使一个线程处于等待状态。在 .NET 运行时确定线程是处于一个安全的挂起位置之前,线程是不会挂起的。 Sleep 则是立即使线程进入等待状态。

最后, Thread.Abort 中止一个线程的执行。在我们的简单例子中,我们还想增加另外一个可以使我们中止程序的按钮。要完成这些,我们所需做的一切便是如下面这样调用 Thread.Abort 方法:

Private Sub Button2_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles Button2.Click

t.Abort()

End Sub

在此便可以看出多线程的能力。用户界面看起来对用户是有响应的,因为它运行在一个线程中而后台的处理程序运行在另一个线程中。取消按钮会立即响应用户的 click 事件同时处理过程被中止。

** 通过多线程的过程传递数据 **

上一个例子展示了一种相当简单的情况。在你编程的时候,多线程有很多需要解决的复杂问题。你将会遇到的一个问题是向传递给 Thread 类构造函数的过程传递数据以及从这个过程传出数据。换言之,你想要在另一个线程上开始的过程不能接收任何参数而且你也不能从这个过程返回任何数据。这是因为传递给线程构造函数的过程不能有任何参数或返回值。为了避开这个问题,将你的过程包装到一个类中,在这个类中此方法的参数被表示成类的一个域。

有一个简单的例子,如果我们有一个计算一个数的平方的过程:

Function Square(ByVal Value As Double) As Double

Return Value * Value

End Function

为了使这个过程可以在一个新线程中使用,我们将其包装到一个类中:

Public Class SquareClass

Public Value As Double

Public Square As Double

Public Sub CalcSquare()

Square = Value * Value

End Sub

End Class

使用这些代码在一个新线程中启动 CalcSquare 过程,代码如下:

Private Sub Button1_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles Button1.Click

Dim oSquare As New SquareClass()

t = New Thread(AddressOf oSquare.CalcSquare)

oSquare.Value = 30

t.Start()

End Sub

注意当线程开始后,我们没有检查类的平方值,因为并不能保证一旦你调用线程 Start 方法,它便会执行。有一些方法可以从另外的线程中获取这个值。最简单的方法是当线程完成时引发一个事件。我们会在下一个部分线程同步中讨论另外一种方法。下面的代码为 SquareClass 增加了事件声明。

Public Class SquareClass

Public Value As Double

Public Square As Double

Public Event ThreadComplete(ByVal Square As Double)

Public Sub CalcSquare()

Square = Value * Value

RaiseEvent ThreadComplete(Square)

End Sub

End Class

在调用代码中捕获这个事件与 VB6 相比没有太大的变化,仍然是用 WithEvents 声明变量并在一个过程中处理事件。变化的部分是用 Handles 关键字声明处理事件的过程并且不再使用像 VB6 中 Object_Event 的命名约定。

Dim WithEvents oSquare As SquareClass

Private Sub Button1_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles Button1.Click

oSquare = New SquareClass()

t = New Thread(AddressOf oSquare.CalcSquare)

oSquare.Value = 30

t.Start()

End Sub

Sub SquareEventHandler(ByVal Square As Double) _

Handles oSquare.ThreadComplete

MsgBox("The square is " & Square)

End Sub

这个方法需要注意的一个问题是处理事件的过程,在本例中是 SquareEventHandler ,将运行在引发事件的线程中,而不是运行在窗体从中执行的线程中。

** 线程同步 **

VB.NET 包含了一些语句用于提供线程的同步。在 Square 的例子中,你可能想同步执行计算的线程以便等到计算完成,这样便可以获得结果。举另外一个例子,如果你在一个单独的线程中对数组进行排序并且在使用这个数组之前要等待这个处理过程结束。为了实现这些同步, VB.NET 提供了 SyncLock 语句和 Thread.Join 方法。

SyncLock 获取了对传递给它的对象引用的独占性锁。通过取得这种独占锁,你可以确保多个线程不会访问共享的数据或是在多个线程上执行代码。一个可以方便地用于获取锁地对象是关联于每个类的 System.Type 对象。可以通过 GetType 方法获得 System.Type 对象:

Public Sub CalcSquare()

SyncLock GetType(SquareClass)

Square = Value * Value

End SyncLock

End Sub

最后, Thread.Join 方法允许你等待一段特定的时间直到一个线程结束。如果线程在你所确定的时间之前完成, Thread.Join 返回 True ,否则的话返回 False 。在 Square 的例子中,如果我们不想引发事件,可以调用 Thread.Join 方法来确定计算是否已经结束。代码如下所示:

Private Sub Button1_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles Button1.Click

Dim oSquare As New SquareClass()

t = New Thread(AddressOf oSquare.CalcSquare)

oSquare.Value = 30

t.Start()

If t.Join(500) Then

MsgBox(oSquare.Square)

End If

End Sub

作者: Matthew Arnheiter 是位于哈肯萨克市 (Hackensack) 的 GoAmerica Communications 公司的一名资深顾问,他还是 The Visual Basic Developer's Guide to Design Patterns and UML (Sybex, 2000) 一书的作者。可以通过 (201)996-1717 x2367 或 Marnheiter ◎ goamerica.net 与他取得联系。

Published At
Categories with Web编程
Tagged with
comments powered by Disqus