Developer
Featured Article |
Start
Building Multi-threaded Applications with Visual Basic .NET
|
 |
By Scott Mauvais,
MCSD
I hate waiting. There is nothing worse than
firing off some long-running activity and having an
application freeze on you while it completes the task. It's
even more annoying when the application doesn't give you any
status about its progress and just leaves you wondering how
long you have to stare at the hour glass. So, do you get up
and refill your coffee or do you just wait it out?
A well-behaved application, on the
other hand, would rarely freeze up during long activities and
if it did, it would provide a thermometer bar or some other
means of communicating its status so that you wouldn't be left
wondering whether you should go grab a lunch or not.
Whether you are building a desktop
application or one that will run on a server, you need to make
sure that it is responsive to your end users. To do this, it
usually means you need to design it so that the
processing-intensive sections are
multi-threaded. |
Unfortunately,
before the release of Microsoft� Visual Studio� .NET, we
Visual Basic� developers were clearly second-class programmers
when it came to threads. If we needed to create a
multi-threaded application we had two choices: we either had
to write it so that it could be hosted in an application
server environment such as IIS or COM+ (or Microsoft
Transaction Server in a Microsoft Windows NT� 4.0 world), or
we had to go talk to one of our C++ colleagues and ask them to
help us out. Because not all applications are well suited for
running in the middle tier, this often left us with nothing
but that ��other�� choice. I don't know about you but the only
thing I hate more than waiting is walking down the hall,
sucking it up, and asking some C++ dev to write a component
for me; it would be weeks before I would hear the end of it,
if ever.
Well,
fortunately, with Visual Basic .NET we have the full power of
the common language runtime (CLR) at our disposal�the same CLR
that all the other .NET languages, including C# and Managed
C++, use. This means we can do just about everything they can
do, including building multi-threaded applications. Better
yet, the Microsoft .NET Framework has taken a lot of the
drudgery out of working in multi-threaded environments, so
building robust, multi-threaded applications is not nearly as
hard as it used to be�second class no more!
In this article
I will cover some of the basics of creating multi-threaded
applications in Microsoft Visual Basic .NET. However, the code
is pretty portable, so if you are using Visual C#� .NET or
some other language it should be easy to follow. There are
essentially three things you need to
learn:
- How to create and manipulate threads.
- How threads affect performance.
- How to design your app so that it uses threads safely
and avoids data corruption.
Here I focus mostly on the first step, although I will
touch on some general guidelines concerning performance. As
for thread safety, that is outside the scope of what I can
cover here.
|
Following
the Multi-Threaded Brick Road |
When I first started down the multi-threaded road, the
first thing I did was pick up a copy of Inside
Microsoft Windows� 2000, Third Edition, by David A.
Solomon and Mark E. Russinovich. This book is an absolutely
essential reference for understanding the architecture of
Microsoft Windows and how it executes your programs. To take
advantage of the multi-threading capability that Microsoft
.NET provides, you need to understand how Microsoft Windows
schedules and prioritizes threads and how your application
will interact with the rest of the Windows environment.
To get a feel for what is in the book and to make sure it
is right for you, check out the book's table
of contents and read Chapter
6: Processes, Threads, and Jobs.
Once you have the Microsoft Windows internals down, you'll
want to pick up a copy of Programming
Microsoft Visual Basic .NET (Core Reference) by Francesco
Balena. This wonderful reference covers everything from the
CLR to GDI+ graphic programming and Windows services. It also
includes advanced optimization techniques and tips for
leveraging the power of the Microsoft Visual Studio .NET
environment. It has an excellent chapter on threading that not
only covers the basics but also dives into more advanced
topics such as passing data between threads and thread
synchronization. Just like Inside Microsoft Windows
2000, you can read a Chapter
21: ADO.NET in Disconnected Mode and review the table
of contents of Programming
Microsoft Visual Basic .NET (Core Reference).
I moved to creating simple applications and started playing
around with the threading constructs. Then I moved on to some
performance testing so that I could learn when to use multiple
threads and when not to. Because that approach worked pretty
well for me, I will use the same one here, but I hope I can
steer you clear of the pitfalls I fell into. |
|
Hello
Multi-threaded World |
The first thing you need to learn is how to create threads.
As I mentioned above, Microsoft .NET makes this pretty easy,
so let's dive right in. Listing 1 is a simple console
application that writes "Hello Multi-threaded World" and a
hash of the thread.
Listing 1: Your First
Multi-threaded Application.
Imports System.Threading Public Class CHello
Public Sub Write()
Console.WriteLine("Hello Multi-Threaded World from thread " _ & System.Threading.Thread.CurrentThread.GetHashCode)
End Sub
End Class
Module Module1
Sub Main()
Dim oHello As New CHello()
'-- ThreadStart is a delegate.
'-- It tells the thread where to start.
Dim ts = New ThreadStart(AddressOf oHello.Write)
'-- Create the threads by passing the ThreadStart
'-- to the Constructor. Dim t1 = New Thread(ts) Dim t2 = New Thread(ts)
'-- The threads aren't running until you '-- start them. After you call start, '-- execution begins in the method referenced '-- in the ThreadStart delegate t1.Start()
t2.Start()
End Sub End Module
Let's step through the code. The CHello class is pretty basic, so I'll skip it.
The main module starts out with a declaration of the CHello class. The next line,
declaring the ThreadStart, is
probably the most confusing one, so let's come back to it
because it will be easier to explain once I have covered the
Thread class.
The next two lines simply declare the Thread objects, t1 and t2, and instantiate them.
Finally, you call the Start
method for each thread. When you call the Start method, the runtime needs to know what
line of code you want the thread to start executing; that is
the role of the ThreadStart
class.
When you instantiate the ThreadStart object, you pass it the address of
the method you want the thread to execute. You then take this
ThreadStart object and pass
it into the Thread's
constructor, which wires the Thread up to the specific method it should
execute.
When I was starting out with Microsoft .NET, I found this
pretty confusing, and all the samples I was able to find tried
to do too many other things such as reading byte arrays off a
TCP socket or inserting rows into a database, which made it
hard to figure out what you really had to do to create a
multi-threaded app. Let me summarize the four things you need
to do to create a thread and start it executing:
- Create the object that contains the method you want your
new thread to execute.
- Instantiate a ThreadStart and use AddressOf(YourClass.YourMethod) to specify the
method.
- Instantiate a Thread and pass in the ThreadStart you just instantiated.
- Call Start on your new Thread.
|
Getting
Control of Your Threads |
Now that you have a thread running, you need to control the
life span of that thread. The most common actions are:
- Abort a thread.
- Change a thread's priority.
- Cause a thread to wait for something.
Abort is the easiest action of the three, so let's cover
that first. Say you have spun off a thread to perform some
long-running calculation and the user decided to cancel, so
you need to stop the process. To do this, simply call the
Abort method: t1.Abort() Changing
a thread's priority is equally easy�ojust set the Priority like this: t1.Priority = ThreadPriority.AboveNormal
You have five different priority levels readily
available: Lowest,
BelowNormal, Normal, AboveNormal, and Highest.
A really good technique for using ThreadPriority and Abort together is to begin some speculative work
on a low priority thread. If it turns out that this work is
not useful, you simply kill the thread. An example here would
be repaginating a large document. Say a user opens a file that
was last saved with a default printer with letter-size paper,
but the current default printer uses legal size. You could
fire off either a Lowest or
BelowNormal thread to begin
repaginating the document. That way, the repagination will not
bother the user because the rest of the application's threads
will run at their original priority and the user would not
have to wait for this long process when she is ready to print.
However, should the user delete the first couple of pages
after you are halfway through your repaginating process, you
could just abort the thread. If you are thinking about using
this technique, you should also read up on the IsBackground property in the System.Threading namespace.
The final common task is to pause a thread and cause it to
wait for something. Depending on what you want it to wait for,
there are several different approaches you can take. Each of
the methods is well documented, so I won't go into too much
detail other than to give a quick overview so that you know
what to look for when you need more info.
- Sleep�Causes a thread
to wait for a specific period of time, usually measured in
milliseconds.
- Suspend�Pauses a thread
indefinitely; you can reactivate it by calling Resume.
- Join�Causes the
executing thread to block until the Join-ed thread completes.
- SpinWait�Used typically
when you want to wait a brief period of time for a lock to
be released. This differs from Sleep in that the thread does not relinquish
the rest of its time slice and thus saves a context switch
if the resource does indeed become available. This is
usually only useful on multi-processor machines or machines
with hyper-threaded CPUs.
|
|
Threading
and Performance |
The thing I really like about building multi-threaded
applications is how much you can affect performance. Done
right, adding additional threads to an underperforming
single-threaded app can often yield impressive gains; done
incorrectly, it can make a so-so app unusable. In other words,
it's pretty easy to be a hero. Conversely. . .well, you get
the point.
The important thing to remember is not to look at threading
as a mountain�you don't want to use it just because it is
there. Besides the performance risks I just mentioned,
properly designing a multi-threaded app to avoid lock
contention and race conditions is often hard. Worse still, if
you get it wrong the bugs are nearly impossible to reproduce
and even harder to debug because they often result from unique
timing conditions. That said, Visual Studio .NET makes it very
easy to code a multi-threaded application, so it is something
you should definitely consider if you have the right app.
So, what is the right application? The first step in
selecting the right application is to re-read David A. Solomon
and Mark E. Russinovich's Inside
Microsoft Windows 2000, Third Edition, especially Chapter
6, "Processes, Threads, and Jobs;" Chapter 7, "Memory
Management;" Chapter 9, "I/O System;" and Chapter 13,
"Networking." With this knowledge fresh in your mind, you will
want to look for an application in which either you process
large units of work that tax the CPU or the app spends a lot
of time waiting on other resources such as the file system or
network IO. Once you've picked out your target app, you'll
want to fire up Performance Monitor and do some load testing
with various numbers of threads. As you are doing this, keep a
close eye on the number of context switches because if you get
too many threads you will start to use all of your resources
simply scheduling the threads.
|
For More
Information |
Now that you have an overview of threading in Visual Basic
.NET, you should be ready to dive in and start developing your
own multi-threaded applications. I started off with walking
you through a simple multi-threaded application that showed
you how to create and utilize threads. Next I walked you
through the methods used to control threads. Finally I covered
some of the performance aspects of multi-threaded applications
you need to consider. The best way to see exactly how easy
Visual Basic .NET is and at the same time ramp up new features
quickly so that you hit the ground running is to pick up a
copy of Francesco Balena's Programming
Microsoft Visual Basic .NET (Core Reference) and Inside
Microsoft Windows 2000, Third Edition, by David A. Solomon
and Mark E. Russinovich.
Microsoft Press� provides in-depth documentation for these
and all the other issues related to developing for .NET. If
you are new to Visual Basic, you will probably be interested
in Microsoft
Visual Basic .NET Step by Step by Michael Halvorson. This
is a great guide that quickly shows you how to build both
Windows Forms and XML Web Service applications the right way.
This book covers everything from upgrading from Visual Basic
6.0 to optimization techniques to deploying your
application.
Another great book for more experienced Visual Basic
developers is Coding
Techniques for Visual Basic .NET by John Connell. This
book goes beyond the simple examples designed to teach you the
language; it is focused on demonstrating key techniques to
help you ship professional-level code.
For more information on developer books, training, and
tools see the following resources:
|
 |
 |
|
Last Updated: Tuesday, June 4, 2002 |
| |
 |
 |
|