View Single Post
Old 04-10-2004, 11:22 AM
excaliber's Avatar
excaliber excaliber is offline
Senior Contributor

* Expert *
Join Date: Nov 2002
Location: Ohio, USA
Posts: 1,828
Default Multithreading in VB.Net

Multithreading is an insanely powerfull tool for developers. Traditionally, it has been limited to use in C/C++. But with the .Net platform, developers can now easily add multithreading to their applications. Multithreading is exactly that, running multiple threads for a single application. This allows many options that just aren't possible to a single threaded application.

Single Threaded
One thread does the work for the entire process. This means that your application will flow in sequential order, from one subroutine to the next. If you have a long subroutine, you will have to wait until it finishes before moving on to the next.

There are two or more threads for the process. This time, your big subroutine can execute in a seperate thread while the rest of the application does other work (used alot for keeping the form responsive while something intensive is being done).

Why though?
Well, lets stop and think about an application that needs to perform something CPU intensive. In a single threaded environment, the intensive subroutine would execute, forcing the rest of the application to be unresponsive until it finishes. This can be worked around by using DoEvents, but that only alleviates the problem a little. In a multithreaded environment, you could spawn a new thread for the intensive subroutine. This thread continues it's work in the background while the rest of the application is not only responsive, but can perform other tasks. You can also set the priority of the spawned thread to be lower if you wish.

The code
At first thought, this seems extremely difficult to do. But in reality, .Net makes it extremely easy to multithread. That's not to say it's not complex, it can get quite complex later, but the fundamentals are dead simple. For our example, we are going to create a function that fills a listbox with a hundred items: the powers of 2. This will be our 'intensive' routine that has been talked about.

First, we need the function itself. It is created like any other function:

Public Function Fill() Dim i as Int32 dim current as Int32 For i = 0 to 100 Listbox1.Items.Add(current) current *=2 Next End Function

Simple enough. Now, to spawn that function as a new thread, do this:

Dim oThread as System.Threading.Thread oThread = new Thread(AddressOf Me.Fill) oThread.Start()

Here we create a new Thread object. AddressOf creates a delegate object of the Fill function (think of it as a type safe, function pointer). We set the thread object equal to the delegate object of Fill, then start the thread. That function will then begin running in its own thread, independant of the calling thread. Neat, eh?

Once the thread is running, you have several methods at your disposal to control it. The first is System.Threading.Thread.Sleep. This causes a thread to 'sleep'. It immediately stops whatever it is doing until it is 'Interupted'. Threading.Thread.Interrupt wakes a thread back up. Here are some code examples:

Public Function Fill() Dim i as Int32 dim current as Int32 current = 2 For i = 0 to 100 Listbox1.Items.Add(current) current *=2 Thread.CurrentThread.Sleep(3000) Next End Function
In this example, we have the Fill function force itself to sleep for 3 seconds (3000 milliseconds). Thread.CurrentThread is a public static property for manipulating the currently running thread.

Dim oThread as System.Threading.Thread oThread = new Thread(AddressOf Me.Fill) oThread.Start() oThread.Sleep(System.Threading.Timeout.Infinite) Dim retValue As MsgBoxResult = MsgBox("Wake Thread?") If retValue = MsgBoxResult.Yes Then oThread.Interrupt() End If
This example forces the newly created thread to sleep (Notice a thread can be forced to sleep either from inside the thread or outside of it) for an infinite amount of time (System.Threading.Timeout.Infinite). It then asks the user if they want to wake it, and if yes, uses the Interrupt() method to do so. If the user would say yes, the thread would resume wherever it left off last.

Two more methods that are very similar to Sleep and Interrupt are Suspend and Resume. These use the same syntax, and work in almost the same way. Unlike Sleep, Suspend will stop the thread when it is safe to stop it. Sleep will stop it immediately, even if it is not safe.

Passing Data
You may have noticed that the function does not have the ability to accept data as arguments. This is because of the AddressOf (there's no way to send arguments). Heres the way around it. We are going to adapt that function to find any number times 2, and put the result in an array instead of a listbox.

But to do this, we need encapsulate it in a class. This is the work around part. Because the function call for threading can't directly pass arguments, we use a class to transfer data around.

Public Class FillClass Private intValue as Int32 Private intReturn(99) as Int32 Private intMultiplier as Int32 Public WriteOnly Property Value() Set(ByVal Value as Int32) intValue = Value End Set End Property Public WriteOnly Property Multiplier() Set(ByVal Value as Int32) intMultiplier = Value End Set End Property Public ReadOnly Property Return() as Int32() Get Return = intReturn End Get End Property Public ReadOnly Property ReturnIndex(ByVal Index as Int32) Get Return = intReturn(index) End Get End Property Public Function Fill() Dim i as Int32 Dim current as Int32 current = intValue For i = 0 to 100 intReturn(i) = current current *= intMultiplier Next End Function End Class
So we have a class that has two internal variables, intValue and intReturn. These have properties that allow objects outside of the class to access them, and one to modify the multiplier. There is also another property to return the value from a specified index (used later). There is also the Fill function, which has been modified.

Here is how we use the class:

Dim oThread as System.Threading.Thread Dim oFill as New FillClass oThread = new Thread(AddressOf oFill.Fill) oFill.Value = 3 oFill.Multiplier = 2 oThread.Start()
Here, we modified created a new instance of the FillClass, then set oThread equal to the AddressOf the oFill.Fill function. Then we used the public property to set the data, then finally started the thread. Notice though that we did not use the Return property after calling start. Here is where you will have to start thinking in a multithreaded mindset. Theres no guarantee that the thread will be finished right after we call it, so using the Return property would be pointless. This is where "Synchronization" comes in. There are several ways, but the easiest way to determine when a thread is completed is an event.

Go back to the FillClass and add this line to the declarations:
Public Event Done(ByVal intReturn() As Int32)

and this line to the end of the function:
RaiseEvent Done(intReturn)

This event will fire when the function is done, and also passes the return value. Now, you have two different ways to retrieve the return value; the property or the event. Another method is to use Thread.Join. We will touch on this in a minute.
RandomIRC - Your neighborhood's friendly IRC channel ( - #code)

"Perl - The only language that looks the same before and after RSA encryption."
Reply With Quote