Sie sind auf Seite 1von 5

http://www.devx.com Printed from http://www.devx.

com/DevX/10MinuteSolution/20365/1954

Add Multithreading to Your VB.NET Applications

Since VB.NET runs with the Comm on Language Runtim e (CLR), it gains many new
capabilities, one of which is the ability to create free- threaded applications.

by Matthew Arnheiter

evelopers have been requesting that Microsoft add more threading functionality to VB for some time—it
will finally happen with VB.NET. VB6 does have support for building multithreaded EXEs, DLLs, and OCXs.
But, the wording for this is somewhat misleading, in that VB6 supports running multiple single-threaded
apartments. An apartment is really just a room in which code is executed and the boundaries of the
apartment restrict the code from accessing anything outside of it.

VB.NET natively supports building free-threaded applications. This means that multiple threads can access
the same set of shared data. The following article will walk you through the basics of multithreading.

While VB6 supports multiple single-threaded apartments, it does not support a free-threading model, which
allows multiple threads to run against the same set of data. There are many situations in which spawning a
new thread to run a background process would increase the usability of your application. This situation is
evident when you want to place a cancel button on a form where a long process can make a form seem
unresponsive.

Since VB.NET runs with the Common Language Runtime (CLR), it gains many new capabilities, one of
which is the ability to create free-threaded applications.

Working with Threads


VB.NET makes it easy to start working with threads. There are some subtleties that we will explore later, but
let's jump in and create a simple form that spawns a new thread to run a background process. The first thing
we will need to do is create the background task that will be run on the new thread. The following code
executes a rather long running process—an infinite loop:

Private Sub BackgroundProcess()


Dim i As Integer = 1

Do While True
ListBox1.Items.Add("Iterations: " + i)
i += 1
Loop
End Sub
This code loops infinitely and adds an item to a listbox on a form for each iteration. As a side note, if you are
not familiar with VB.NET then you may have noticed a few other things in this code that you could not do in
VB6:

Assign values to a variable when declaring the variable Dim i As Integer = 1


Use the += operator i += 1 Instead of i = i + 1
The Call keyword has been removed

Once we have a worker process, we need to assign this block of code to a new thread and start its
execution. To do this we use the Thread object that is part of the System.Threading namespace in the .NET
framework classes. When we instantiate a new Thread class we pass it a reference to the code block we
want to execute in the constructor of the Thread class. The following code creates a new Thread object and
passes it a reference to BackgroundProcess:

Dim t As Thread
t = New Thread(AddressOf Me.BackgroundProcess)
t.Start()

The AddressOf operator creates a delegate object to the BackgroundProcess method. A delegate within
VB.NET is a type-safe, object-oriented function pointer. After the thread has been instantiated, you begin the
execution of the code by calling the Start() method of the thread.

Keep It Under Control


After the thread is started, you have some control over the state of it by using methods of the Thread object.
You can pause a thread's execution by calling the Thread.Sleep method. This method takes an integer value
that determines how long the thread should sleep. If you wanted to slow down the addition of items to the
listbox in the example above, place a call to the sleep method in this code:

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 is a public static property that allows you to retrieve a reference to the currently running thread.

You can also place a thread into the sleep state for an indeterminate amount of time by calling Thread.Sleep
(System.Threading.Timeout.Infinite). To interrupt this sleep you can call the Thread.Interrupt method.

Similar to Sleep and Interrupt are Suspend and Resume. Suspend allows you to block a thread until another
thread calls Thread.Resume. The difference between Sleep and Suspend is that the latter does not
immediately place a thread in the wait state. The thread does not suspend until the .NET runtime determines
that it is in a safe place to suspend it. Sleep will immediately place a thread in a wait state.

Lastly, Thread.Abort stops a thread from executing. In our simple example, we would want to add another
button on the form that allows us to stop the process. To do this all we would have to do is call the
Thread.Abort method as follows:

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


ByVal e As System.EventArgs) Handles Button2.Click
t.Abort()
End Sub

This is where the power of multithreading can be seen. The UI seems responsive to the user because it is
running in one thread and the background process is running in another thread. The cancel button
immediately responds to the user's click event and processing stops.

Passing Data Through Multithreaded Procedures


The last example shows a rather simple situation. Multithreading has many complications that you have to
work out when you program. One issue that you will run into is passing data to and from the procedure
passed to the constructor of the Thread class. That is to say, the procedure you want to kick off on another
thread cannot be passed any parameters and you cannot return data from that procedure. This is because
the procedure you pass to the thread constructor cannot have any parameters or return value. To get around
this, wrap your procedure in a class where the parameters to the method are written as fields of the class.

A simple example of this would be if we had a procedure that calculated the square of a number:

Function Square(ByVal Value As Double) As Double


Return Value * Value
End Function

To make this procedure available to be used in a new thread we would wrap it in a class:

Public Class SquareClass


Public Value As Double
Public Square As Double

Public Sub CalcSquare()


Square = Value * Value
End Sub
End Class

Use this code to start the CalcSquare procedure on a new thread. following code:

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

Notice that after the thread is started, we do not inspect the square value of the class, because it is not
guaranteed to have executed once you call the start method of the thread. There are a few ways to retrieve
values back from another thread. The easiest way is to raise an event when the thread is complete. We will
examine another method in the next section on thread synchronization. The following code adds the event
declarations to the 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

Catching the events in the calling code has not changed much from VB6, you still declare the variables
WithEvents and handle the event in a procedure. The part that has changed is that you declare that a
procedure handles the event using the Handles keyword and not through the naming convention of
Object_Event as in VB6.

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

The one thing to note with this method is that the procedure handling the event, in this case
SquareEventHandler, will run within the thread that raised the event. It does not run within the thread from
which the form is executing.

Synchronizing the Threads


VB.NET contains a few statements to provide synchronization of threads. In the Square example, you would
want to synchronize the thread performing the calculation in order to wait for the calculation to complete so
you can retrieve the result. Another example would be if you sort an array on a different thread and you would
wait for that process to complete before using the array. To perform these synchronizations, VB.NET
provides the SyncLockEnd SyncLock statement and the Thread.Join method.

SyncLock gains an exclusive lock to an object reference that is passed to it. By gaining this exclusive lock you
can ensure that multiple threads are not accessing shared data or that the code is executing on multiple
threads. A convenient object to use in order to gain a lock is the System.Type object associated with each
class. The System.Type object can be retrieved using the GetType method:

Public Sub CalcSquare()


SyncLock GetType(SquareClass)
Square = Value * Value
End SyncLock
End Sub

Lastly, the Thread.Join method allows you to wait for a specific amount of time until a thread has completed.
If the thread completes before the timeout that you specify, Thread.Join returns True, otherwise it returns
False. In the square sample, if we did not want to raise events, we could call the Thread.Join method to
determine if the calculation has finished. The code would look like the following:

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

The one thing to note with this method is that the procedure handling the event, in this case
SquareEventHandler, will run within the thread that raised the event. It does not run within the thread from
which the form is executing.

Matthew Arnheiter is a Senior Consultant at GoAmerica Communications (www.goamerica.net) of


Hackensack, NJ. He is also the author of "The Visual Basic Developer's Guide to Design Patterns and UML"
(Sybex, 2000). He can be reached here.

DevX is a division of Internet.com.


© Copyright 2010 Internet.com. All Rights Reserved. Legal Notices