开发者一直要求微软为VB加入更多的多线程功能,对于VB.NET也是这样。VB6已经支持建立多线程的EXE、DLL和OCX。不过使用多线程这个词语,可能也不太确切。因此VB6仅支持运行多个单线程的单元。一个单元实际上是代码执行的空间,而单元的边界限制了代码访问任何单元以外的事物。
VB.NET就不同了,它支持建立自由线程(free-threaded)的应用。这意味着多个线程可以访问同样一套的共享数据。本文的以下部分将讨论一下多线程的一些基本点。
问题
虽然VB6支持多个单线程的单元,不过它并不支持一个自由线程的模型,即不允许多个线程使用同一套数据。在许多的情况下,你需要建立一个新的线程来进行后台的处理,这样可提高应用的可用性,否则,一个长的处理就可以令程序的响应变得很慢,例如你按下表格上的一个取消按钮,却很久都没有响应。
解决办法
由于VB.NET使用了CLR(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的有一些区别:
. 在声明变量Dim i As Integer = 1时赋值
. 使用+=操作符i += 1代替i = i + 1
. 没有使用Call关键字
一旦我们拥有了一个工作的处理,我们就需要将这段代码分配给一个线程处理,并且启动它。为此我们要使用线程对象(Thread object),它是.NET架构类中System.Threading命名空间的一部分。在实例化一个新的线程类时,我们将要在线程类构造器执行的代码块的一个引用传送给它。以下的代码创建一个新的线程对象,并且将BackgroundProcess的一个引用传送给它:
Dim t As Thread
t = New Thread(AddressOf Me.BackgroundProcess)
t.Start()
AddressOf操作符创建了一个到BackgroundProcess方法的委派对象。在VB.NET中,一个委派是一个类型安全、面向对象的函数指针。在实例化该线程后,你可以通过调用线程的Start()方法来开始执行代码。
控制线程
在线程启动后,你可以通过线程对象的一个方法来控制它的状态。你可以通过调用Thread.Sleep方法来暂停一个线程的执行,这个方法可以接收一个整型值,用来决定线程休眠的时间。拿前面的例子来说,如果你想让列表项目增加的速度变慢,可以在其中放入一个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是一个public static的属性值,可让你得到当前运行线程的一个引用。
你还可以通过调用Thread.Sleep (System.Threading.Timeout.Infinite)来让线程进入休眠状态,有点特别的是,这个调用的休眠时间是不确定的。要中断这个休眠,你可以调用Thread.Interrupt方法。
与休眠和中断类似的是挂起和恢复。挂起可让你暂停一个线程,直到另一个线程调用Thread.Resume为止。休眠和挂起的区别是,后者并不立刻让线程进入一个等待的状态,线程并不会挂起,直到.NET runtime认为现在已经是一个安全的地方来挂起它了,而休眠则会立刻让线程进入一个等待的状态。
最后要介绍的是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
这就是多线程的强大之处。用户界面的响应很好,因为它运行在一个单独的线程中,而后台的处理运行在另外一个线程中。在用户按下取消按钮时,便会马上得到响应,并且停止处理。
上面的例子只是一个相当简单的应用。在编程时,你还需要使用到多线程的许多复杂特性。其中的一个问题是如何将程序的数据由线程类的构造器传入或者传出,也就是说,对于放到另外一个线程中的过程,你既不能传参数给它,也不能由它返回值。这是由于你传入到线程构造器的过程是不能拥有任何的参数或者返回值的。为了解决这个问题,可以将你的过程封装到一个类中,这样方法的参数就可使用类中的字段。
这里我们举一个简单的例子,如果我们要计算一个数的平方,即:
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
要注意到,在线程启动后,我们并没有检查类中的square值,因为即使你调用了线程的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提供了几个方法。在上面的平方例子中,你要与执行计算的线程同步,以便等待它执行完并且得到结果。另一个例子是,如果你在其它线程中排序一个数组,那么在使用该数组前,你必须等待该处理完成。为了进行这些同步,VB.NET提供了SyncLock声明和Thread.Join方法。
SyncLock可得到一个对象引用的唯一锁,只要将该对象传送给SyncLock就行了。通过得到这个唯一锁,你可以确保多个线程不会访问共享的数据或者在多个线程上执行的代码。要得到一个锁,可使用一个较为便利的对象--与每个类关联的System.Type对象。System.Type对象可通过使用GetType方法得到:
Public Sub CalcSquare()
SyncLock GetType(SquareClass)
Square = Value * Value
End SyncLock
End Sub
另一个是Thread.Join方法,它可让你等待一个特定的时间,直到一个线程完成。如果该线程在你指定的时间前完成了,Thread.Join将返回True,否则它返回False。在平方的例子中,如果你不想使用触发事件的方法,你可以调用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
对于这种方法,要注意的是处理事件的过程,在这个例子中的是SquareEventHandler,将运行在产生该事件的线程中。它并不是运行在表格执行的线程中。
开发者一直要求微软为VB加入更多的多线程功能,对于VB.NET也是这样。VB6已经支持建立多线程的EXE、DLL和OCX。不过使用多线程这个词语,可能也不太确切。因此VB6仅支持运行多个单线程的单元。一个单元实际上是代码执行的空间,而单元的边界限制了代码访问任何单元以外的事物。
VB.NET就不同了,它支持建立自由线程(free-threaded)的应用。这意味着多个线程可以访问同样一套的共享数据。本文的以下部分将讨论一下多线程的一些基本点。
问题
虽然VB6支持多个单线程的单元,不过它并不支持一个自由线程的模型,即不允许多个线程使用同一套数据。在许多的情况下,你需要建立一个新的线程来进行后台的处理,这样可提高应用的可用性,否则,一个长的处理就可以令程序的响应变得很慢,例如你按下表格上的一个取消按钮,却很久都没有响应。
解决办法
由于VB.NET使用了CLR(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的有一些区别:
. 在声明变量Dim i As Integer = 1时赋值
. 使用+=操作符i += 1代替i = i + 1
. 没有使用Call关键字
一旦我们拥有了一个工作的处理,我们就需要将这段代码分配给一个线程处理,并且启动它。为此我们要使用线程对象(Thread object),它是.NET架构类中System.Threading命名空间的一部分。在实例化一个新的线程类时,我们将要在线程类构造器执行的代码块的一个引用传送给它。以下的代码创建一个新的线程对象,并且将BackgroundProcess的一个引用传送给它:
Dim t As Thread
t = New Thread(AddressOf Me.BackgroundProcess)
t.Start()
AddressOf操作符创建了一个到BackgroundProcess方法的委派对象。在VB.NET中,一个委派是一个类型安全、面向对象的函数指针。在实例化该线程后,你可以通过调用线程的Start()方法来开始执行代码。
控制线程
在线程启动后,你可以通过线程对象的一个方法来控制它的状态。你可以通过调用Thread.Sleep方法来暂停一个线程的执行,这个方法可以接收一个整型值,用来决定线程休眠的时间。拿前面的例子来说,如果你想让列表项目增加的速度变慢,可以在其中放入一个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是一个public static的属性值,可让你得到当前运行线程的一个引用。
你还可以通过调用Thread.Sleep (System.Threading.Timeout.Infinite)来让线程进入休眠状态,有点特别的是,这个调用的休眠时间是不确定的。要中断这个休眠,你可以调用Thread.Interrupt方法。
与休眠和中断类似的是挂起和恢复。挂起可让你暂停一个线程,直到另一个线程调用Thread.Resume为止。休眠和挂起的区别是,后者并不立刻让线程进入一个等待的状态,线程并不会挂起,直到.NET runtime认为现在已经是一个安全的地方来挂起它了,而休眠则会立刻让线程进入一个等待的状态。
最后要介绍的是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
这就是多线程的强大之处。用户界面的响应很好,因为它运行在一个单独的线程中,而后台的处理运行在另外一个线程中。在用户按下取消按钮时,便会马上得到响应,并且停止处理。
上面的例子只是一个相当简单的应用。在编程时,你还需要使用到多线程的许多复杂特性。其中的一个问题是如何将程序的数据由线程类的构造器传入或者传出,也就是说,对于放到另外一个线程中的过程,你既不能传参数给它,也不能由它返回值。这是由于你传入到线程构造器的过程是不能拥有任何的参数或者返回值的。为了解决这个问题,可以将你的过程封装到一个类中,这样方法的参数就可使用类中的字段。
这里我们举一个简单的例子,如果我们要计算一个数的平方,即:
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
要注意到,在线程启动后,我们并没有检查类中的square值,因为即使你调用了线程的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提供了几个方法。在上面的平方例子中,你要与执行计算的线程同步,以便等待它执行完并且得到结果。另一个例子是,如果你在其它线程中排序一个数组,那么在使用该数组前,你必须等待该处理完成。为了进行这些同步,VB.NET提供了SyncLock声明和Thread.Join方法。
SyncLock可得到一个对象引用的唯一锁,只要将该对象传送给SyncLock就行了。通过得到这个唯一锁,你可以确保多个线程不会访问共享的数据或者在多个线程上执行的代码。要得到一个锁,可使用一个较为便利的对象--与每个类关联的System.Type对象。System.Type对象可通过使用GetType方法得到:
Public Sub CalcSquare()
SyncLock GetType(SquareClass)
Square = Value * Value
End SyncLock
End Sub
另一个是Thread.Join方法,它可让你等待一个特定的时间,直到一个线程完成。如果该线程在你指定的时间前完成了,Thread.Join将返回True,否则它返回False。在平方的例子中,如果你不想使用触发事件的方法,你可以调用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
对于这种方法,要注意的是处理事件的过程,在这个例子中的是SquareEventHandler,将运行在产生该事件的线程中。它并不是运行在表格执行的线程中。
很长时间以来,开发人员一直要求微软为VB增加更多的线程功能——这一点在VB.NET中终于实现了。VB6不支持创建VB.Net多线程的EXE、DLL以及OCX。但这种措词容易引起误解,这是因为VB6支持执行多个单线程的单元。一个单元实际上是代码执行的场所而且单元的边界限制了外部代码对单元内部的访问。
VB.NET支持创建自由线程的应用程序。这意味着多个线程可以访问同一个共享的数据集。本文将带领你了解VB.NET多线程的基本内容。
虽然VB支持多个单线程的单元,但并不支持允许多个线程在同一个数据集上运行的自由线程模型。在很多情况下,产生一个运行后台处理程序的新线程会提高应用程序的可用性。一种很显然的情况就是当执行一个可能使窗体看起来停止响应的长过程时,你一定会想在窗体上放置一个取消按钮。
解决方法
由于VB.NET使用公共语言运行时(Common Language Runtime),它增强了很多新的特性,其中之一便是创建自由线程应用程序的能力。
在VB.NET中,开始使利用线程进行工作是很容易的。稍后我们会探究一些精妙之处,我们先创建一个简单的窗体,它生成一个执行后台处理程序的新线程。我们需要做的第一件事是将要在新线程上运行的后台处理程序。下面的代码执行一个相当长的运行过程——一个无限循环:
这段代码无限地循环并在每次循环中向窗体上的列表框中增加一个条目。如果你对VB.NET不熟悉的话,便会发现这段代码中有一些在VB6中无法完成的事:
◆在声明变量时对其赋值 Dim i As Integer=1
◆使用+=操作符 i+=1代替了i=i+1
◆Call关键字已经被去除了
一旦我们有了一个工作过程,便需要将这段代码指派给一个新的线程并开始它的执行。完成这项工作,我们需要使用Thread对象,它是.NET框架类中System.Threading命名空间的一部分。当实例化了一个新的Thread类时,我们向其传递一个引用,这个引用指向我们想要在Thread类的构造函数中执行的代码块。下面的代码创建一个新的Thread对象并将指向BackgroundProcess的引用传递给它:
AddressOf操作符为BackgroundProcess方法创建了一个委派对象。委派在VB.NET中是一种类型安全的、面向对象的函数指针。在线程被实例化之后,你可以通过调用线程的Start()方法开始执行代码。
使线程处于控制之下
当线程开始之后,你可以通过使用Thread对象的方法对其状态进行一定的控制。你可以通过调用Thread.Sleep方法暂停线程的执行。这个方法接收一个表示线程将要休眠多长时间的整型数值。如果在上例中你想要减缓列表框条目的添加,在代码中放置一个对此方法的调用:
CurrentThread是一个公共静态属性,它可以使你获取一个对当前运行线程的引用。
你还可以通过调用Thread.Sleep (System.Threading.Timeout.Infinite)使一个线程处于一种时间不确定的休眠状态。要中断这种休眠,可以调用Thread.Interrupt 方法。
类似与Sleep和Interrupt的是Suspend和Resume。Suspend允许你阻塞一个线程直到另外的线程调用Thread.Resume。Sleep和Suspend之间的区别在于后者不是立即使一个线程处于等待状态。在.NET运行时确定线程是处于一个安全的挂起位置之前,线程是不会挂起的。Sleep则是立即使线程进入等待状态。
最后,Thread.Abort中止一个线程的执行。在我们的简单例子中,我们还想增加另外一个可以使我们中止程序的按钮。要完成这些,我们所需做的一切便是如下面这样调用Thread.Abort方法:
在此便可以看出多线程的能力。用户界面看起来对用户是有响应的,因为它运行在一个线程中而后台的处理程序运行在另一个线程中。取消按钮会立即响应用户的click事件同时处理过程被中止。
通过VB.NET多线程的过程传递数据
上一个例子展示了一种相当简单的情况。在你编程的时候,多线程有很多需要解决的复杂问题。你将会遇到的一个问题是向传递给Thread类构造函数的过程传递数据以及从这个过程传出数据。换言之,你想要在另一个线程上开始的过程不能接收任何参数而且你也不能从这个过程返回任何数据。这是因为传递给线程构造函数的过程不能有任何参数或返回值。为了避开这个问题,将你的过程包装到一个类中,在这个类中此方法的参数被表示成类的一个域。
有一个简单的例子,如果我们有一个计算一个数的平方的过程:
为了使这个过程可以在一个新线程中使用,我们将其包装到一个类中:
使用这些代码在一个新线程中启动CalcSquare过程,代码如下:
注意:当线程开始后,我们没有检查类的平方值,因为并不能保证一旦你调用线程Start方法,它便会执行。有一些方法可以从另外的线程中获取这个值。最简单的方法是当线程完成时引发一个事件。我们会在下一个部分线程同步中讨论另外一种方法。下面的代码为SquareClass增加了事件声明。
在调用代码中捕获这个事件与VB6相比没有太大的变化,仍然是用WithEvents声明变量并在一个过程中处理事件。变化的部分是用Handles关键字声明处理事件的过程并且不再使用像VB6中Object_Event的命名约定。
这个方法需要注意的一个问题是处理事件的过程,在本例中是SquareEventHandler,将运行在引发事件的线程中,而不是运行在窗体从中执行的线程中。
线程同步
VB.NET包含了一些语句用于提供线程的同步。在Square的例子中,你可能想同步执行计算的线程以便等到计算完成,这样便可以获得结果。举另外一个例子,如果你在一个单独的线程中对数组进行排序并且在使用这个数组之前要等待这个处理过程结束。为了实现这些同步,VB.NET提供了SyncLock语句和Thread.Join方法。
SyncLock获取了对传递给它的对象引用的独占性锁。通过取得这种独占锁,你可以确保多个线程不会访问共享的数据或是在多个线程上执行代码。一个可以方便地用于获取锁地对象是关联于每个类的System.Type对象。可以通过GetType方法获得System.Type对象:
最后,Thread.Join方法允许你等待一段特定的时间直到一个线程结束。如果线程在你所确定的时间之前完成,Thread.Join返回True,否则的话返回False。在Square的例子中,如果我们不想引发事件,可以调用Thread.Join方法来确定计算是否已经结束。代码如下所示:
欢迎光临 信息发布软件,b2b软件,广告发布软件 (http://postbbs.com/) | Powered by Discuz! X3.2 |