microsoft press home   All Products  |   Support  |   Search  |   microsoft.com Home  
 
  Microsoft Press Home  |   Register Books  |   Site Index  |   All Books  |

 

Advanced Search
Hot topics:
Books about:
Books for:
Products for:



Developer Featured Article
Start Building Multi-threaded Applications with Visual Basic .NET
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:

  1. Create the object that contains the method you want your new thread to execute.
  2. Instantiate a ThreadStart and use AddressOf(YourClass.YourMethod) to specify the method.
  3. Instantiate a Thread and pass in the ThreadStart you just instantiated.
  4. 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:

Top of Page
 
Last Updated: Tuesday, June 4, 2002