Go Back  Xtreme Visual Basic Talk > Visual Basic .NET (2002/2003/2005/2008, including Express editions) > .NET Knowledge Base > Tutors' Corner > Multithreading in VB.Net


Reply
 
Thread Tools Display Modes
  #1  
Old 04-10-2004, 12:22 PM
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


Introduction
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.

Multithreaded
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:

Code:
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:

Code:
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:

Code:
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.


Code:
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.

Code:
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:

Code:
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:
Code:
Public Event Done(ByVal intReturn() As Int32)

and this line to the end of the function:
Code:
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 (irc.randomirc.com - #code)

"Perl - The only language that looks the same before and after RSA encryption."
Reply With Quote
  #2  
Old 04-10-2004, 12:24 PM
excaliber's Avatar
excaliber excaliber is offline
Senior Contributor

* Expert *
 
Join Date: Nov 2002
Location: Ohio, USA
Posts: 1,828
Default

A big problem with multithreaded applications is data sharing. If you look at file manipulations, the I/O routines lock the target file to prevent other processes from modifying the data while the current process is modifying it. If it didn't there would be data corruption and such (one process overwrites what another just wrote, so that they conflict later). The same can happen in multithreaded applications. To prevent this, you lock your variables as well, using the SyncLock statement. As an explanation, MSDN says it best:

Quote:
Monitor objects are used to ensure that a block of code runs without being interrupted by code running on other threads. In other words, code in other threads cannot run until code in the synchronized code block has finished.

Suppose, for example, that you have a program that repeatedly and asynchronously reads data and displays the results. With operating systems that use preemptive multitasking, a running thread can be interrupted by the operating system to allow time for some other thread to run. Without synchronization, it is possible that you could get a partially updated view of the data if the object that represents the data is modified by another thread while the data is being displayed. Monitor objects guarantee that a section of code will run without being interrupted. Visual Basic .NET provides the SyncLock and End SyncLock statements to simplify access to monitor objects.
SyncLock puts an exclusive lock on the object that is passed to it, preventing other threads from accessing it. SyncLock must be passed a reference object, and a handy one is the System.Type of the class itself. Here is how it would look in our example:

Code:
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() 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() SynchLock GetType(FillClass) Dim i as Int32 Dim current as Int32 current = intValue For i = 0 to 100 intReturn(i) = current current *= intMultiplier Next End SyncLock End Function End Class
For instance, if this code would be run without a SyncLock, and another thread modifies intMultiplier (by using the property Multiplier) while this thread was running, the results would be incorrect. SyncLock prevents other processes from accessing the data until the block ends.

Now, for a more complex problem using Thread.Join as mentioned earlier. Join is a method that allows you to waith till a thread completes before proceeding (blocks the current thread). It can also be passed a number as an argument. It waits that number in milliseconds, then returns true if the thread is finished, or false if not.

In this example, we are going to spawn two new threads, then make another to compute the results from the previous two.
Code:
Dim oThread(2) as System.Threading.Thread Dim oFill(2) as New FillClass 'start the first thread, setting 2 as the value oThread(0) = new Thread(AddressOf oFill(0).Fill) oFill(0).Value = 2 oFill(0).Multiplier = 2 oThread(0).Start() 'start the second, using 3 as the value oThread(1) = new Thread(AddressOf oFill(1).Fill) oFill(1).Value = 3 oFill(1).Multiplier = 2 oThread(1).Start() 'wait until the first thread is done oThread(0).Join 'waith until the second thread is done oThread(1).Join oThread(2) = new Thread(AddressOf oFill(2).Fill) oFill(2).Value = oThread(0).ReturnIndex(4) - oThread(1).ReturnIndex oFill(2).Multiplier = 2 oThread(2).Start() oThread(2).Join Dim i as Int32, retVal() as Int32 retVal = oThread(2).Return For i = 0 to UBound(retVal) Listbox1.Items.Add(retVal(i)) Next

Ok, here's what happened. First, we declare two an array of threads, and an array of classes. Next we set the first thread to the first class' function, then fill the value, and finally start it. We do the same for the next one. Then, we call the oThread(0).Join, which will block until the thread finishes its execution. We do the same for the next thread, ensuring they are both done. Now we create a new thread and set its value, which is based on the previous two results (which is why we had to wait for the threads to finish execution, so we could use their values). We run, block until its done, then display the results in a Listbox.

Conclusion
This is just a shallow look at multithreading, and will get you started for simple projects. For more advanced projects with complicated and complext multithreading, advanced synchronization is needed. This is done using things such as Wait Handles, Mutext Objects, Synch Events, Interlocked Classes and ReaderWriter Locks. Unfortunately, I'm still reading over this material and don't know enough to post a tutorial on it quite yet.
__________________
RandomIRC - Your neighborhood's friendly IRC channel (irc.randomirc.com - #code)

"Perl - The only language that looks the same before and after RSA encryption."
Reply With Quote
  #3  
Old 12-20-2006, 06:45 AM
Banjo's Avatar
Banjo Banjo is offline
Hell's Angel

Retired Moderator
* Guru *
 
Join Date: Jul 2001
Location: Yorkshire, UK
Posts: 10,394
Default

It should be noted that the very first example in this article (Filling a Listbox) will not actually work properly. There is all sorts of complications with accessing UI elements across threads.

The solution is Delegates. Take a look at excaliber's other article:
http://www.xtremevbtalk.com/showthread.php?t=159976
__________________
A wise one man once said "what you talking about dog breath"
Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
Passing variables and controls using WinForms and VB.NET Bucky Tutors' Corner 0 02-24-2004 04:45 PM
VB6 vs VB.NET geo1st487 Tech Discussions 10 01-31-2004 10:37 PM
Open VB.NET 2002 project within VB.NET 2003 geo1st487 .NET General 2 01-10-2004 05:13 PM
VB.Net Office Integration FAQ Mike Rosenblum .NET Office Automation 2 12-19-2003 01:17 PM
A VB6 to VB.NET conversion sample Helmar General 0 11-14-2001 06:27 PM

Advertisement:





Free Publications
The ASP.NET 2.0 Anthology
101 Essential Tips, Tricks & Hacks - Free 156 Page Preview. Learn the most practical features and best approaches for ASP.NET.
subscribe
Programmers Heaven C# School Book -Free 338 Page eBook
The Programmers Heaven C# School book covers the .NET framework and the C# language.
subscribe
Build Your Own ASP.NET 3.5 Web Site Using C# & VB, 3rd Edition - Free 219 Page Preview!
This comprehensive step-by-step guide will help get your database-driven ASP.NET web site up and running in no time..
subscribe
 
 
-->