Multithreading in .NET Applications

Mark Strawmyer Presents: .NET Nuts & Bolts


Multithreading is a powerful tool for creating high performance applications, especially those that require user interaction. Microsoft .NET has broken down the barriers that once existed in creating multithreaded applications. In this installment of the .NET Nuts & Bolts column we'll explore multithreading with the .NET Framework and some of the things it can provide. This is part one of a two-part topic. In this article we will cover the background on threading along with an example of how multiple threads can be used to improve an application's user interface.

Definition of Threads

In order to understand multithreading we first have to understand threads. Each application/program that is running on a system is a process. The process for each program consists of one or more threads that are given processor time to do the work. Each thread contains all of the context information required by the system in order to execute the program instructions.

The operating system is responsible for scheduling and execution of threads. Remember the days of Windows 3.x? A single process thread could, and usually did, monopolize all of the processor time. The system would just sit and wait for the thread to complete prior to executing any other processes.

Newer operating systems, such as Windows 2000, support pre-emptive multitasking which allocates each thread a time slice. When the time slice of the currently executing thread has elapsed, the thread is suspended by the operating system, context of the thread is saved, context of another thread is loaded, and the other thread then resumes execution according to its previous state. This gives the appearance that multiple threads are executing at the same time and helps prevent the system from becoming unresponsive from a single thread (end task anyone?). On systems that have more that one processor threads are distributed across all of the processors so there really are multiple threads executing at the same time.

Threading Model Background

There are many threading models available and a lot of theory behind them. I do not claim to be an expert on any threading model, nor am I going to attempt to explain them in depth in this article. However, we will focus on the threading models that are common to the Microsoft Win32 based environments, and I'll provide brief explanation about each.

Single Threaded

Single threaded means there is only one thread within the process and it is doing all of the work for the process. The process must wait for the current execution of the thread to complete before it can perform another action.

Single threaded results in system idle time and user frustration. For example, assume we are saving a file to a remote network using a single threaded application. Since there is only a single thread in the application, the application will not be able to do anything else while the file is being stored in the remote location. Thus the user waits and begins to wonder if the application is ever going to resume.

Apartment Threading (Single Threaded Apartment)

Apartment threaded means there are multiple threads within the application. In single threaded apartment (STA) each thread is isolated in a separate apartment underneath the process. The process can have any number of apartments that share data through a proxy. The application defines when and for how long the thread in each apartment should execute. All requests are serialized through the Windows message queue such that only a single apartment is accessed at a time and thus only a single thread will be executing at any one time. STA is the threading model that most Visual Basic developers are familiar with because this is the threading model available to VB applications prior to VB.NET. You can think of it like an apartment building full of a row of one room apartments that are accessible one at a time through a single hallway. The advantage this provides over single threaded is that multiple commands can be issued at one time instead of just a single command, but the commands are still sequentially executed.

Free Threading (Multi Threaded Apartment)

Free threaded applications were limited to programming languages such as C++ until the release of Microsoft .NET. The free threaded/Multi Threaded Apartment (MTA) model has a single apartment created underneath the process rather than multiple apartments. This single apartment holds multiple threads rather than just a single thread. No message queue is required because all of the threads are a part of the same apartment and can share data without a proxy. You can think of it like a building with multiple rooms that are all accessible once you are inside the building. These applications typically execute faster than single threaded and STA because there is less system overhead and can be optimized to eliminate system idle time.

These types of applications are more complex to program. The developer must provide thread synchronization as part of the code to ensure that threads do not simultaneously access the same resources. A condition known as a race condition can occur when a thread accesses a shared resource and modifies the resource to an invalid state and then another thread accesses the shared resource and uses it in the invalid state before the other thread can return the resource to a valid state. Therefore it is necessary to place a lock on a resource to prevent other threads from accessing the resource until the lock has been removed. However, this can lead to a deadlock situation where two threads are competing for resources and neither can proceed. For example, thread #1 has a resource locked and is waiting for another resource that is currently locked by thread #2. Thread #2 happens to be waiting for the resource locked by thread #1. Thus, both threads are waiting on the other and neither will be allowed to proceed. The only way to avoid situations like these is through good design and testing.

Using Multiple Threads

There are advantages and disadvantages to the use of multiple threads. Using multiple threads is not always advantageous, so you should make the determination for yourself whether or not there is enough benefit for your application(s).

Advantages of Multiple Threads

  • Improved responsiveness — When it comes to application performance I am a firm believer that perception is reality. If the user perceives that my application is slow, then it that means it is in reality slow. If the application is performing operations that take a perceivably long time to complete, these operations can be put into a separate thread which will allow the application to continue to be responsive to the user.
  • Faster application — Multiple threads can lead to improved application performance. For example, if there are a number of calculations to be performed or the contents of a file are being processed, then there the application can be made faster by performing multiple operations at the same time.
  • Prioritization — Threads can be assigned a priority which would allow higher priority tasks to take precedence over lower priority tasks.

Disadvantages of Multiple Threads

  • Programming and debugging is more complex — With multithreaded applications the programmer must account for race conditions and deadlocks.
  • Threads add overhead to the system — In order for the operating system to track a large number of threads it is going to consume processor time. If there are too many threads then each thread may not be given enough time to execute during its time slice. In addition, each thread is scheduled for execution less frequently due to the volume and time slice committed to each thread.

Threading Example

To demonstrate the use of multiple threads we will use a sample windows application that will show how a user interface can be made to respond differently when performing the same action.

Sample User Interface

The application has a user interface containing a list box. When either the "Run Nonthreaded" or "Run Multithreaded" buttons are pressed the buttons will be temporarily disabled and the list box will be filled with "Hello World" messages. There will be a processing delay thrown in between each entry in the list box. The interface also contains a button to test the interface responsiveness while the list box is being populated. The sample user interface is as follows:

Sample Code Listing

Here is a listing of all of the code behind the user interface. The click events of the "Run Nonthreaded" and "Run Multithreaded" buttons result in the same method being executed. The only difference is that when the "Run Multithreaded" button is pressed the method is executed in a new thread.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace CodeGuru.Multithreaded
{
 /// <remarks>
 /// Example demonstrating the use of threads in a windows form.
 /// </remarks>
 public class Form1 : System.Windows.Forms.Form
 {
   private System.Windows.Forms.Button nonThreadedButton;
   private System.Windows.Forms.Button multiThreadedButton;
   private System.Windows.Forms.ListBox responseList;
   private System.Windows.Forms.Button testResponseButton;
   private System.Windows.Forms.Label responseCountLabel;
   private System.Windows.Forms.Label responseLabel;
   /// <summary>
   /// Required designer variable.
   /// </summary>
   private System.ComponentModel.Container components = null;

   public Form1()
   {
      //
       // Required for Windows Form Designer support
       //
       InitializeComponent();
   }

   /// <summary>
   /// Clean up any resources being used.
   /// </summary>
   protected override void Dispose( bool disposing )
   {
      if( disposing )
      {
         if (components != null) 
         {
            components.Dispose();
         }
      }
      base.Dispose( disposing );
   }

   #region Windows Form Designer generated code
   /// <summary>
   /// Required method for Designer support - do not modify
   /// the contents of this method with the code editor.
   /// </summary>
   private void InitializeComponent()
   {
      this.responseList = new System.Windows.Forms.ListBox();
      this.nonThreadedButton = new System.Windows.Forms.Button();
      this.multiThreadedButton = new System.Windows.Forms.Button();
      this.testResponseButton = new System.Windows.Forms.Button();
      this.responseCountLabel = new System.Windows.Forms.Label();
      this.responseLabel = new System.Windows.Forms.Label();
      this.SuspendLayout();
      // 
      // responseList
      // 
      this.responseList.Name = "responseList";
      this.responseList.Size = new System.Drawing.Size(120, 134);
      this.responseList.TabIndex = 6;
      // 
      // nonThreadedButton
      // 
      this.nonThreadedButton.Location = 
                         new System.Drawing.Point(160, 8);
      this.nonThreadedButton.Name = "nonThreadedButton";
      this.nonThreadedButton.Size = new System.Drawing.Size(120, 23);
      this.nonThreadedButton.TabIndex = 1;
      this.nonThreadedButton.Text = "Run Nonthreaded";
      this.nonThreadedButton.Click += new 
             System.EventHandler(this.nonThreadedButton_Click);
      // 
      // multiThreadedButton
      // 
      this.multiThreadedButton.Location = 
                      new System.Drawing.Point(160, 40);
      this.multiThreadedButton.Name = "multiThreadedButton";
      this.multiThreadedButton.Size = new System.Drawing.Size(120, 23);
      this.multiThreadedButton.TabIndex = 3;
      this.multiThreadedButton.Text = "Run Multithreaded";
      this.multiThreadedButton.Click += new 
            System.EventHandler(this.multiThreadedButton_Click);
      // 
      // testResponseButton
      // 
      this.testResponseButton.Location = 
                             new System.Drawing.Point(160, 160);
      this.testResponseButton.Name = "testResponseButton";
      this.testResponseButton.Size = new System.Drawing.Size(120, 23);
      this.testResponseButton.TabIndex = 4;
      this.testResponseButton.Text = "Test Responsiveness";
      this.testResponseButton.Click += new 
     System.EventHandler(this.testResponseButton_Click);
      // 
      // responseCountLabel
      // 
      this.responseCountLabel.Location = 
                         new System.Drawing.Point(112, 160);
      this.responseCountLabel.Name = "responseCountLabel";
      this.responseCountLabel.Size = new System.Drawing.Size(24, 23);
      this.responseCountLabel.TabIndex = 5;
      this.responseCountLabel.Text = "0";
      // 
      // responseLabel
      // 
      this.responseLabel.Location = new System.Drawing.Point(8, 160);
      this.responseLabel.Name = "responseLabel";
      this.responseLabel.TabIndex = 7;
      this.responseLabel.Text = "Click Response:";
      // 
      // Form1
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(292, 221);
      this.Controls.AddRange(new System.Windows.Forms.Control[] {
                 this.responseLabel,
                 this.responseCountLabel,
                 this.testResponseButton,
                 this.multiThreadedButton,
                 this.nonThreadedButton,
                 this.responseList});
      this.Name = "Form1";
      this.Text = "Multithreaded Example";
      this.ResumeLayout(false);

   }
   #endregion

   /// <summary>
   /// The main entry point for the application.
   /// </summary>
   [STAThread]
   static void Main() 
   {
      Application.Run(new Form1());
   }

   /*
    * Responde to the nonThreadedButton click event.
    */
   private void nonThreadedButton_Click(object sender, 
                                        System.EventArgs e)
   {
      this.FillList();
   }

   /*
    * Responde to the multiThreadedButton click event.
    */
   private void multiThreadedButton_Click(object sender, 
                                          System.EventArgs e)
   {
      // Launch a thread to do the update
      System.Threading.Thread sampleThread = 
           new System.Threading.Thread(
             new System.Threading.ThreadStart(this.FillList));
      sampleThread.Start();
   }

   /*
    * Respond to the testResponseButton click event.
    */
   private void testResponseButton_Click(object sender, 
                                         System.EventArgs e)
   {
      this.responseCountLabel.Text = 
      Convert.ToString(
            Convert.ToInt32(this.responseCountLabel.Text) + 1);
   }

   /*
    * Deplay processing for the specificed number of milliseconds
    */
   private void Delay(int v_MilliSeconds)
   {
      long startTime = Environment.TickCount;
      while( Environment.TickCount — startTime < v_MilliSeconds ) {}
   }

   /*
    * Fill the listbox with hello world buttons.  Pause for a period
    * of time between each message.
    */
   private void FillList()
   {
      this.responseList.Items.Clear();
      this.responseCountLabel.Text = "0";
      this.ToggleButtons(false);
      for( int i = 0; i < 10; i++ )
      {
         responseList.Items.Add("Hello World #" + i);
         this.Delay(1000);
      }
      this.ToggleButtons(true);
   }

   /*
    * Toggle the form buttons on or off accordingly.
    */
   private void ToggleButtons(bool v_Toggle)
   {
      this.nonThreadedButton.Enabled = v_Toggle;
      this.multiThreadedButton.Enabled = v_Toggle;
   }
 }
}

Testing the Sample

Run the sample application and press the "Run Nonthreaded" button. While the process is running try pressing the "Test Responsiveness" button. You'll notice the user interface is not updating the list as items are added, nor is the response counter updated when you test the responsiveness. Once processing is complete, the list box will populate and the test responsiveness actions will be executed.

Now press the "Run Multithreaded" button. While the process is running you'll immediately notice that the list box is updating as the process executes. As you press the "Test Responsiveness" button you will notice the button is responsive and the counter updates as you click on the button. This clearly shows how using threads can allow our application to be more responsive in certain scenarios.

Possible Enhancements

There is much more to threading than what was presented within this article. I showed an example related to improved responsiveness to the user. I did not show any examples relating to providing a listener type of application such as a web server. I also mentioned, but did not show examples of how to lock a resource to avoid a race condition nor did I talk about the differences between managed and unmanaged threads. Hopefully this article gave you the background to understand multithreading and serves as a spring board to get you interested in using multiple threads in Microsoft .NET.

Future Columns

The next column is going to be part 2 on threading. It will cover the threading classes in the .NET Framework and topics such as synchronization and locking. If you have something in particular that you would like to see explained here you could reach me at mstrawmyer@crowechizek.com

About the Author

Mark Strawmyer, MCSD, MCSE (NT4/W2K), MCDBA is a Senior Architect of .NET applications for large and mid-size organizations. Mark is a technology leader with Crowe Chizek in Indianapolis, Indiana. He specializes in architecture, design and development of Microsoft-based solutions. You can reach Mark at mstrawmyer@crowechizek.com.

# # #



About the Author

Mark Strawmyer

Mark Strawmyer is a Senior Architect of .NET applications for large and mid-size organizations. He specializes in architecture, design and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the fifth year in a row. You can reach Mark at mark.strawmyer@crowehorwath.com.

Comments

  • Nice intro, poor follow thru - no explanation of code

    Posted by virasana on 02/27/2005 04:57am

    Thanks for the intro on multi-threading. You provide a code sample, which had a minor error Ihad to clean up before the code would work. (try to run this yourself in VS 2003)! You also don't provide any explanation of how your multi-threading code works! Try expanding on this article. www.sharpit.co.za

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • Relying on outside companies to manage your network and server environments for your business and applications to meet the needs and demands of your users can be stressful. This is especially true as many Managed Hosting organizations fail to meet their service level agreements. Read this Forrester total economic impact report and learn what makes INetU different and how they exceed their customers' managed hosting expectations.

Most Popular Programming Stories

More for Developers

RSS Feeds