Was this page helpful?
Your feedback about this content is important. Let us know what you think.
Additional feedback?
1500 characters remaining
Export (0) Print
Expand All
Debugging: Root Out Elusive Production Bugs with These Effective Techniques
Smart Tags: Simplify UI Development with Custom Designer Actions in Visual Studio
Ten Essential Tools: Visual Studio Add-Ins Every Developer Should Download Now
XML Comments: Document Your Code in No Time At All with Macros in Visual Studio
Expand Minimize

Using Threads

Visual Studio .NET 2003
 

Greg Ewing
Clarity Consulting Inc.

March 2002

Summary: This article explains the different models of threading — single, apartment, and free — and the use of each model. It also introduces a code sample in C# that uses threading to help you write applications that will take advantage of threading. The article also discusses important issues involved in multithreading code. (9 printed pages)

Download Sample

Contents

Introduction
Threading Background
Sample Application
Issues with Multithreaded Code
Conclusion

Introduction

Writing a multithreaded Microsoft® Message Queuing (MSMQ) trigger application has traditionally been a daunting task. The advent of the .NET Framework threading and messaging classes makes it easier than ever. These classes allow you to write multithreaded applications with any language targeting the .NET Framework. Previously, tools like Microsoft Visual Basic® had very limited support for threading. As a result, you were forced to use C++ to write multithreaded code, build a less-than-ideal solution in Visual Basic consisting of multiple processes or ActiveX DLLs, or abandon multithreading altogether. With the .NET Framework, you can build rich multithreaded applications regardless of the language that you choose to use.

This article will step through the process of building a multithreaded application that listens for and processes messages from a Microsoft message queue. The article will focus on the two namespaces System.Threading and System.Messaging. The sample code is written in C#, but it could be easily converted to the language of your choice.

Threading Background

There are three basic models of threading in the Win32 environment: single, apartment, and free.

Single Threading

Some of the first applications that you wrote were probably single threaded, with the only thread corresponding to your application's process. A process can be defined as the instance of an application that owns that application's memory space. Most Windows applications are single threaded with a single thread doing all of the work.

Apartment Threading

Apartment threading is the next level of complexity in threading models. Code marked for apartment threading can execute on its own thread, confined to its own apartment. A thread can be defined as an entity owned by a process that is scheduled for processing time. In the apartment-threading model, all threads operate within the confines of their own subsections of the main application's memory. This model allows multiple instances of your code to execute simultaneously but independently. For example, before .NET, Visual Basic was limited to creating apartment-threaded components and applications.

Free Threading

Free threading is the most complex threading model. In the free-threading model, multiple threads can call into the same methods and components at the same time. Unlike apartment threading, they are not confined to separate memory spaces. One instance where you might use a free-threaded object is when an application must do a large number of very similar yet independent mathematical calculations. In this case, you would spawn multiple threads to perform the calculations using the same code instance. C++ developers are likely the only application developers who have written free-threaded applications, because languages like Visual Basic 6.0 make it almost impossible to do so.

Working with Threading Models

One way to conceptualize the threading models is to consider the task of moving from one house to another. If you take a single-threaded approach, you do all the work yourself, from packing to carrying boxes to unpacking. If you operate in an apartment-threaded model, you invite some of your closest friends to help you. Each friend works in a separate room and cannot help the people working in other rooms. They are in charge of their own space and moving supplies. If you go for a free-threaded approach, you would invite those same friends over to help, but all of them work in any of the rooms at any time, sharing packing supplies. In this analogy, your house is the process in which all your threads operate, each of your friends is an instance of your code, and the moving supplies are your application's resources and variables.

This example illustrates some advantages and disadvantages of the different models. Apartment threading is faster than single threading because multiple instances of your component are doing the work. Free threading is faster and more efficient than apartment threading in some situations because everything is happening at once and all the supplies are shared. This can lead to problems, however, if multiple threads are changing shared resources. Imagine that one person started to use a box and packed it with items from your kitchen, and then another friend came along and used that same box to pack things from your bedroom. The first friend labeled the box 'Kitchen,' and then your second friend overwrites that label with 'Bathroom'. When you unpack, you will end up with kitchen supplies in your bathroom.

Sample Application

The first step is to review the design of the sample application. The application will spawn multiple threads, each of which will listen for messages on an MSMQ queue. This example uses two classes, the main Form class and a custom MQListen class. The Form class will handle the user interface as well as create, manage, and destroy the worker threads. The MQListen class will contain all the code, including the message queuing aspects, necessary for the worker threads to run.

Preparing the Application

  • To start the application, open Visual Studio .NET and create a new C# Windows Application named MultiThreadedMQListener. Open the properties for the form and name it QueueListenerForm. Once the initial form is drawn, drop two labels, two buttons, a status bar, and two text boxes on it. Name the first textbox Server and the second Queue. Name the first button StartListening and the second StopListening. You can leave the status bar with the default name, statusBar1.
  • Next, add a reference to the System.Messaging namespace by clicking the Project menu and then clicking Add Reference. Find System.Messaging.Dll in the list of .NET components and select it. This namespace contains the classes used to communicate with MSMQ queues.
  • Next, add a new class to the project by clicking the File menu and then clicking Add New Item. Select the Class template and name it MQListen. Add the following using statements to the top of the class:
    // C#
    using System.Threading;
    using System.Messaging;
    

    The System.Threading namespace will allow you to access all the necessary threading functionality, in this case, the Thread class and the ThreadInterruptException constructor. Many more advanced features are available from this namespace, but they are out of the scope of this article. The System.Messaging namespace provides access to the MSMQ functionality, including sending and receiving messages from a queue. In this sample, you will use the MessageQueue class to receive messages. You must also add using System.Threading to the main form code.

With all the references in place, you are ready to start writing the code.

Worker Threads

The first step is to build the MQListen class that encapsulates all the thread work. Insert the following code into MQListen.

// C#
public class MQListen
{   
   private string m_MachineName;
   private string m_QueueName;
      
   // Constructor accepts the necessary queue information.
   public MQListen(string MachineName, string QueueName)
   {
      m_MachineName = MachineName;
      m_QueueName = QueueName;
   }
    

   // One and only method that each thread uses to listen for MQ messages
   public void Listen()
   {
      // Create a MessageQueue object.
      System.Messaging.MessageQueue MQ  = new 
System.Messaging.MessageQueue();

      // Set the path property on the MessageQueue object.
      MQ.Path = m_MachineName + "\\private$\\" + m_QueueName;
            
      // Create a Message object.
System.Messaging.Message Message = new    
System.Messaging.Message();        
      // Repeat until Interrupt received.
      while (true)
      {
         try
         {
// Sleep in order to catch the interrupt if it has been thrown.
System.Threading.Thread.Sleep(100);
// Set the Message object equal to the result from the receive function.
// Timespan(days, hours, minutes, seconds).
            Message = MQ.Receive(new TimeSpan(0, 0, 0, 1));
                             
            // Display the received message's label
System.Windows.Forms.MessageBox.Show(" Label: " + Message.Label);
                  
         }
         catch (ThreadInterruptedException e)
         {
// Catch the ThreadInterrupt from the main thread and exit.
            Console.WriteLine("Exiting Thread");
            Message.Dispose();
            MQ.Dispose();
            break;
         }
         catch (Exception GenericException)
         {
            // Catch any exceptions thrown in receive.
            Console.WriteLine(GenericException.Message);
         }
      }
   }
}

Code Discussion

The MQListen class has one function other than the constructor. This function encapsulates all the work that each worker thread will perform. In the main thread, you pass a reference to this function to the thread constructor so that it can be executed when the threads are started.

The first thing that Listen does is set up a message queue object. The MessageQueue constructor is overloaded with three implementations. The first implementation takes two arguments: a string argument that specifies the location of the queue on which to listen, and a Boolean that indicates whether the first application that accesses the queue should be granted exclusive read permission to the queue. The second implementation takes only the queue path argument, and the third implementation takes no arguments. For simplicity, you can use the third implementation and assign the path on the next line.

Once you have a reference to the queue, you must create a message object. The message constructor has three implementations as well. The first two implementations are available for cases in which you want to write a message to a queue. They take an object that will go in the body of the message and an IMessageFormatter object that defines how the object should be serialized into the body of the message. In this case, you are reading from the queue, so initialize an empty message object.

With the objects initialized, you enter the main loop that will do all the work. Later, when the main thread calls Interrupt to stop these threads, they will only be interrupted if they are in a wait, sleep, or join state. If they are not in one of these three states, then they will not be interrupted until the next time that they enter one. To ensure that the worker threads enter a wait, sleep, or join state, call the Sleep method, which is found in the System.Threading namespace. The Sleep method should be very familiar to C++ developers and Visual Basic developers who have used the Windows API sleep function in the past. It takes only one argument: the number of milliseconds that the thread should sleep. If you never called Sleep, then the workers would never enter a state from which they could receive the interrupt request and would continue indefinitely until you shut down the process manually.

There are two implementations of the MQ Receive method. The first implementation takes nothing and will wait infinitely for a message to be received. The second implementation, used in this sample, specifies a timeout with a TimeSpan object. The TimeSpan constructor has four arguments: days, hours, minutes, and seconds. In this example, the Receive method waits for one second before timing out and returning.

When a message is received, it is assigned to the message object created earlier and is then available for processing. This sample opens a message box with the label and discards the message. If you want to adapt this code for real usage, then you would put any message handling code there.

When the worker thread receives the Interrupt request, it will throw a ThreadInterruptedException exception. To catch that exception, wrap both the Sleep and the Receive functions in a try-catch block. You should specify two catches: the first to catch the interrupt exception, and the second to handle any error exceptions that are caught. When the interrupt exception is caught, first write to the debug window that the thread is exiting. Next, call the Dispose method on both the queue object and the message object to ensure that all memory is cleaned up and sent to the garbage collector. Finally, break out of the while loop.

As soon as the function exits the while loop, the associated thread will end with code 0. In the debug window, you will see a message such as 'The thread '<name>' (0x660) has exited with code 0 (0x0)'. The thread is now out of context and is automatically destroyed. Neither the main thread nor the worker thread has to do anything special to clean it up.

Main Form

The next step is to add the code to the form to spawn the worker threads and start the MQListen class on each one. Start by adding the following function to the form:

// C#
private void StartThreads()
{
   int LoopCounter; // Thread count
   StopListeningFlag = false; // Flag to track whether the workers 
                               // should be stopped.

   // Declare an array of 5 threads to be worker threads.
   Thread[] ThreadArray = new Thread[5];

// Declare the class that contains all the code for the worker threads.
   MQListen objMQListen = new
MQListen(this.ServerName.Text,this.QueueName.Text); 

   for (LoopCounter = 0; LoopCounter < NUMBER_THREADS; LoopCounter++)
   {
      // Create a Thread object.
      ThreadArray[LoopCounter] = new Thread(new
ThreadStart(objMQListen.Listen));
      // Starting the thread invokes the ThreadStart delegate.
      ThreadArray[LoopCounter].Start();
   }

   statusBar1.Text = LoopCounter.ToString() + " listener threads started";

   while (!StopListeningFlag)
   {
      // Wait for the user to press the stop button.
      // While waiting, let the system handle other events.
      System.Windows.Forms.Application.DoEvents();
   }

   statusBar1.Text = "Stop request received, stopping threads";
   // Send an interrupt request to each thread.
   for (LoopCounter = 0;LoopCounter < NUMBER_THREADS; LoopCounter++)
   {      
      ThreadArray[LoopCounter].Interrupt();
   }

   statusBar1.Text = "All Threads have been stopped";
}

Code Discussion

This function starts by creating an array of threads consisting of five items. This array will hold references to all the thread objects for future use.

The constructor for the MQListen class takes two arguments: the machine name, which hosts the message queues, and the name of the queue to which you want to listen. The constructor assigns these arguments using values from the text boxes.

To create the threads, you enter a loop to initialize each thread object. The Thread constructor requires that you pass it a delegate that points to the function to be invoked when the thread's Start method is called. You want the thread to start using the MQListen.Listen function, but it is not a delegate. To satisfy the thread constructor's requirements, you must pass a ThreadStart object, which will create a delegate given a function name. In this case, pass the ThreadStart object a reference to the MQListen.Listen function. Now that this array element is initialized, immediately call Start to kick off the thread.

Once all the threads have started, update the status bar on the form with an appropriate message. With the threads running and listening on the queue, the main thread waits for the user to ask for the application to stop listening. To accomplish this, it enters a while loop until you click the StopListening button, which changes the value of StopListeningFlag. Inside of this wait loop, allow the application to do any other processing that it might need to do by using the Forms.Application.DoEvents method. For those familiar with Visual Basic, this is the same as the old DoEvents method. For those familiar with C++, this is equivalent to writing a MSG pump.

When the user clicks the StopListening button, this loop will exit and enter the thread shutdown code. To shut down every thread, the code must loop through the thread array and send an interrupt signal to each thread. Inside this loop, call the Interrupt method on each thread object in the array. Until this method is called, the code in the MQListen class will continue to execute normally. Because of this, you can call Interrupt on each worker regardless of whether it is in the middle of processing something else. The thread class will handle each thread's cleanup once it has completed. The final task is to update the status bar on the main form before exiting.

You now need to add the code behind the buttons. Add the following code to the StartListening button's Click event:

// C#
statusBar1.Text = "Starting Threads";
StartThreads();

This will update the status bar and call the StartThreads method. For the StopListening button, all you need to do is set the StopListeningFlag to True with this code:

// C#
StopListeningFlag = true;

The last step is to add the form-level variable for the StopListeningFlag. Add this line to the top of the form code:

// C#
private bool StopListeningFlag = false;

To test your application, you can download MQWrite, a sample application to write to a message queue.

Issues with Multithreaded Code

Now that you have worked through the sample code, you have the tools required to write your own multithreaded application. Threading can dramatically improve both the performance and scalability of some applications. With this power, you must be aware of some dangers of threading. A few situations where using threads can be detrimental to your application do exist. Threads may down operation, cause unexpected results, or even cause your application to stop responding.

If you have multiple threads, make sure that they are not waiting for each other to reach a certain point or to finish. If not done correctly, this can lead to a deadlock state where neither thread will ever finish because each is waiting for the other.

If multiple threads will require access to a resource that cannot be shared easily, such as a floppy disk drive, serial port, or an infrared port, you probably want to avoid using threads or use one of the more advanced threading tools such as synclocks or mutexes to manage concurrency. If two threads attempt to access one of these resources at the same time, then one will fail to acquire the resources or cause data corruption.

Another common problem with threading is a race condition. If one thread is writing data to a file while a second thread is reading from that file, then you do not know which thread will finish first. This is referred to as a race condition because the two threads are racing to the end of the file. If the reading thread were to get ahead of the writing thread, then unknown results would be returned.

When using threads, you should also consider whether all the threads would be able to work completely independent of each other. If they do need to pass data back and forth, you want to be careful that this data is relatively simple. When you pass complex objects, you start to incur heavy marshaling costs to move those objects back and forth. This will generate overhead for the operating system to manage as well as reduce general performance.

Another concern is the transition cost of handing off your code to another developer. While .NET does make threading easier, note that the next developer who maintains your code must understand threading to work with it. While this is not a reason to avoid threads, it is an excellent reason to supply adequate code comments.

While none of these issues should discourage you from threading by itself, they are all issues you should keep in mind when you are designing your application and deciding whether to use threads or not. Unfortunately it is out of the scope of this white paper to discuss some of the methods to avoid these pitfalls. If you have decided to use threads but are running into some of the problems mentioned above, check out synclocks or mutexes to see if they will solve your problem or lead you to another solution.

Conclusion

With this information, you can write applications that will take advantage of threading. When doing that, however, keep in mind the issues involved. When used properly, threading can make applications perform and scale much better than a single-threaded application. When used incorrectly, however, threading can have the exact opposite effect and can result in unstable applications.

Show:
© 2015 Microsoft