September 17, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Multithreading in .NET Applications, Part 2

  • June 19, 2003
  • By Mark Strawmyer
  • Send Email »
  • More Articles »

Multithreading is a powerful design 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. The last installment of the .NET Nuts & Bolts column was Part 1 of the exploration of multithreading with the .NET Framework. In that article, we covered the background of threading, benefits of threading, and provided a demonstration. This article serves as Part 2 of our exploration of multithreading. We will look at the basic methods involved with working with threads, along with the synchronization of thread activity.

Working with Threads

In order to successfully work with threads, we must understand some basic thread operations. A list of the commonly used methods from the System.Threading.Thread class is provided below:

  • Abort raises a ThreadAbortException to tell the thread to terminate. A thread cannot be restarted once it has been terminated.
  • Join blocks the calling thread until this thread terminates
  • Sleep blocks the thread from execution for a specified number of milliseconds
  • Start causes the thread to begin execution

It is important to notice how the list does not include methods to stop or free the thread. This is handled automatically by the .NET common language runtime (CLR) when the thread execution is complete.

Sample Code Listing

The following code contains a sample console-based application. The application creates a new thread that calls a method to display continuous messages to the console window until the thread is complete. The thread could be terminated through a call to the Abort() method, but for this example we'll just loop a certain number of times and stop. The application contains example usage of most of the methods listed above.

using System;using System.Collections;using System.Threading;namespace CodeGuru.MultithreadedPart2{  /// <remarks>  /// Example console application demonstrating the basic methods  /// used with threads.  /// </remarks>  class ThreadExample  {    /// <summary>    /// The main entry point for the application.    /// </summary>    [STAThread]    static void Main(string[] args)    {      // Create the thread and indicate to use the DisplayMessage      // method      ThreadExample example = new ThreadExample();      Thread testThread = new Thread(new                          ThreadStart(example.DisplayMessage));      // Start the thread      testThread.Start();      // Wait until the thread finishes before continuing      testThread.Join();      Console.WriteLine("\r\nTest thread has finished");    }    /// <summary>    /// Display a message to the console window.    /// </summary>    public void DisplayMessage()    {      for( int i = 0; i < 20; i++ )      {        Console.WriteLine("DisplayMessage is running in its own                           thread.");      }    }  }}

Testing the Sample

Run the sample console application provided above. The output will look roughly as follows:

Synchronizing Threads

Now that we have an understanding of some of the methods involved, we can look at the complex issue of synchronization. When using multiple threads, it is possible that different threads could access the same object simultaneously and leave the object in an invalid state. It is also possible a thread may do something that would interrupt what another thread is doing with a resource. Thus, it is important to be able to control access to blocks of code and resources. This control is known as synchronization and objects that use it correctly are known as thread safe.

Protecting a Code Region

When two or more threads need to access the same object, it is possible that one thread may do something that would interrupt what the other thread is doing. Thus, it is imperative to be able to control access to blocks of code to prevent such items from occurring. This functionality is exposed through the static methods of the System.Threading.Monitor class. The Monitor class is used to synchronize blocks of code, instance methods, and static methods. The locking is performed based on an object, which means it will not lock properly on value types such as int, string, and so on. When the object is locked, no other thread can utilize the object. The region of code is established by a call to Monitor.Enter at the start and released by a call to Monitor.Exit.

Sample Code Listing

The following code contains a sample console-based application. The application creates two separate threads. One of the threads adds numbers into a queue, and the other thread removes numbers from the same queue. The monitor class is used so that the queuing and de-queuing threads are not trying to add and remove from the queue at the same time. The queuing thread is started first and adds an item to the queue. The de-queuing thread is started and is waiting for the queue to be released. When the queue is released, the de-queuing thread removes the item from the queue and then releases the queue. Each thread continues using, releasing, then waiting for the queue again, such that 10 items are added and removed from the queue.

using System;using System.Collections;using System.Threading;namespace CodeGuru.MultithreadedPart2{  /// <remarks>  /// Example console application demonstrating the use of a  /// monitor.  /// </remarks>  class MonitorExample  {    // Constant representing the maximum number of items to enqueue    const int MAX_QUEUE_SIZE = 10;    // Queue to hold items    Queue _queue;    /// <summary>    /// Constructor    /// </summary>    public MonitorExample()    {      this._queue = new Queue();     }    /// <summary>    /// The main entry point for the application.    /// </summary>    [STAThread]    static void Main(string[] args)    {      // Start two threads to add and remove queue items      MonitorExample test = new MonitorExample();      Thread enqueueThread = new Thread(new                                        ThreadStart(test.Enqueue));      Thread dequeueThread = new Thread(new                                        ThreadStart(test.Dequeue));      enqueueThread.Start();      dequeueThread.Start();      // Wait to the end of the two threads then print the number of      // queue elements      enqueueThread.Join();      dequeueThread.Join();    }    /// <summary>    /// Add items to the queue.    /// </summary>    public void Enqueue()    {      int counter = 0;      Monitor.Enter(this._queue);      while( counter < MAX_QUEUE_SIZE )      {        // Wait while the queue is busy        Monitor.Wait(this._queue);        // Add one item to the queue        Console.WriteLine("Adding item {0} to queue",                           counter.ToString());        this._queue.Enqueue(counter++);      // Release the queue      Monitor.Pulse(this._queue);    }    Monitor.Exit(this._queue);  }    /// <summary>    /// Remove items from the queue.    /// </summary>    public void Dequeue()    {      Monitor.Enter(this._queue);      // Release the queue because we just locked it      // setting up the region lock      Monitor.Pulse(this._queue);      // Wait while the queue is busy      // and exit on the timeout when the first thread stopped      while(Monitor.Wait(this._queue, 500))      {        // Remove the first item and display it        int counter = (int)this._queue.Dequeue();        Console.WriteLine("Removed item {0} from queue",                           counter.ToString());        // Release the queue        Monitor.Pulse(this._queue);      }      Monitor.Exit(this._queue);    }  }}

Testing the Sample

Run the sample console application provided above. The output will look roughly as follows:





Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel