Extending .NET Threads

I l @ ve RuBoard

Although .NET offers a unified approach to threads for all languages that target the .NET platform, there still lacks a true cohesiveness and encapsulation with regards to a thread object. By creating a small abstract base class to represent a worker thread, the ability to extend the basic thread support will prove useful when developing larger applications.

WorkerThread Class

Creating a simple abstract class to represent a worker thread is not that difficult. There are a couple of key concepts that the worker thread base class needs to provide, the first of which is the ability to associate data with a particular thread. This will allow a worker thread to be created and data for the worker thread to be specified. The next item of business is the ability to stop the worker thread in some predictable manner. The simplest way to accomplish this is to provide a Stop method that causes a ThreadAbortException to be generated within the Run method of the worker thread. Listing 5.4.9 shows the implementation for a basic worker thread class.

Listing 5.4.9 Abstract WorkerThread Class
 1: using System;  2: using System.Threading;  3:  4: namespace SAMS.Threading  5: {  6:  7:  8:     /// <summary>  9:     /// Encapsulate a worker thread and data 10:     /// </summary> 11:     public abstract class WorkerThread { 12: 13:         private object    ThreadData; 14:         private Thread    thisThread; 15: 16: 17:         //Properties 18:         public object Data { 19:            get {  return ThreadData; } 20:            set {  ThreadData = value; } 21:         } 22: 23:         public object IsAlive { 24:             get {  return thisThread == null ? false : thisThread.IsAlive; } 25:         } 26: 27:         /// <summary> 28:         /// Constructors 29:         /// </summary> 30: 31:         public WorkerThread( object data ) { 32:             this.ThreadData = data; 33:         } 34: 35:         public WorkerThread( ) { 36:             ThreadData = null; 37:         } 38: 39:         /// <summary> 40:         /// Public Methods 41:         /// </summary> 42: 43:         /// <summary> 44:         /// Start the worker thread 45:         /// </summary> 46:         public void Start( ) { 47:             thisThread = new Thread( new ThreadStart( this.Run ) ); 48:             thisThread.Start(); 49:         } 50: 51:         /// <summary> 52:         /// Stop the current thread.  Abort causes 53:         /// a ThreadAbortException to be raised within 54:         /// the thread 55:         /// </summary> 56:         public void Stop( ) { 57:             thisThread.Abort( ); 58:             while( thisThread.IsAlive ) ; 59:             thisThread = null; 60:         } 61: 62:         /// <summary> 63:         /// To be implemented by derived threads 64:         /// </summary> 65:         protected abstract void Run( ); 66:     } 67: } 

The implementation of the WorkerThread class shows the ease in which creating a wrapper class for existing thread support can be accomplished. A wrapper class is jargon for a class that wraps up some basic functionality to extend a common type or class. The most important method within the WorkerThread class is the abstract Run method. The Run method is meant to be implemented in a derived class and is where the actual work to be performed by the thread will take place.

Dining Philosophers

To put the WorkerThread class to use, a classical threading synchronization problem, known as the Dining Philosophers, will be implemented. In the Dining Philosophers problem, a group of philosophers are seated at a circular table. They each eat and think, eat and think. To eat, however, it is necessary for a philosopher to obtain the chopstick on his or her right and left. Figure 5.4.2 shows the arrangement of the philosophers and the chopsticks.

Figure 5.4.2. The Dining Philosophers.

graphics/0504fig02.gif

Of course, the synchronization issue arises when each philosopher has one chopstick and sits waiting for the other chopstick. This situation is known as a deadlock. In a deadlock situation, various threads are waiting on resources that cannot be obtained due to some circular lock pattern. Thus, the goal is to ensure that a philosopher will only retain both chopsticks and not hold on to a single chopstick. Listing 5.4.9 shows the implementation of the Dinning Philosophers using the WorkerThread base class.

Listing 5.4.10 The Dining Philosophers
 1: using System;  2: using System.Threading;  3: using SAMS.Threading;  4:  5: namespace DiningPhilosophers  6: {  7:  8:     public struct PhilosopherData {  9:         public int        PhilosopherId; 10:         public Mutex      RightChopStick; 11:         public Mutex      LeftChopStick; 12:         public int        AmountToEat; 13:         public int        TotalFood; 14:     } 15: 16: 17: 18:     public class Philosopher : WorkerThread 19:     { 20:         public Philosopher( object data ) : base( data ) {  } 21: 22:         //Implement the abstract Run Method 23:         protected override void Run( ) { 24: 25:             PhilosopherData pd = (PhilosopherData)Data; 26:             Random r = new Random( pd.PhilosopherId ); 27:             Console.WriteLine("Philosopher { 0}  ready", pd.PhilosopherId ); 28:             WaitHandle[] chopSticks =  new WaitHandle[] {  pd.LeftChopStick, graphics/ccc.gif pd.RightChopStick } ; 29: 30:             while( pd.TotalFood > 0 ) { 31:                //Get both chop sticks 32:                WaitHandle.WaitAll( chopSticks ); 33:                Console.WriteLine("Philosopher { 0}  eating { 1}  of { 2}  food", graphics/ccc.gif pd.PhilosopherId, pd.AmountToEat, pd.TotalFood ); 34:                pd.TotalFood -= pd.AmountToEat; 35:                Thread.Sleep( r.Next(1000,5000) ); 36: 37:                //Release the chopsticks 38:                Console.WriteLine("Philosopher { 0}  thinking", pd.PhilosopherId); 39:                pd.RightChopStick.ReleaseMutex( ); 40:                pd.LeftChopStick.ReleaseMutex( ); 41: 42:                //Think for a random time length 43:                Thread.Sleep( r.Next(1000,5000) ); 44:             } 45:             Console.WriteLine("Philosopher { 0}  finished",  pd.PhilosopherId ); 46:         } 47:     } 48: 49: 50:     public class Restaurant { 51: 52:         public static void Main( ) { 53:             Mutex[] chopSticks = new Mutex[5]; 54: 55:             //init the chopSticks 56:             for( int i = 0; i < 5; i++ ) 57:                 chopSticks[i] = new Mutex( false ); 58: 59:             //Create the Five Philosophers 60:             for( int i = 0; i < 5; i++ ) { 61:                PhilosopherData pd; 62:                pd.PhilosopherId = i + 1; 63:                pd.RightChopStick = chopSticks[ i - 1 >= 0 ? ( i - 1 ) : 4 ]; 64:                pd.LeftChopStick = chopSticks[i]; 65:                pd.AmountToEat = 5; 66:                pd.TotalFood = 35; 67:                Philosopher p = new Philosopher( pd ); 68:                p.Start( ); 69:             } 70: 71:             Console.ReadLine( ); 72:         } 73:     } 74: } 

The Dining Philosophers example introduces the use of the static method WaitHandle.WaitAll to acquire both chopsticks. Effectively, the WaitAll method will not return until it is able to acquire both of the WaitHandle -derived objects, in this case a pair of Mutex classes for each chopstick. The WaitAll method also provides two overrides that allow for specifying the timeout value to wait for the operation and a Boolean flag to determine the exit context. WaitAll proves useful when it is necessary to acquire several resources before performing an operation and prevent a deadlock situation that can arise due to circular waiting.

I l @ ve RuBoard


C# and the .NET Framework. The C++ Perspective
C# and the .NET Framework
ISBN: 067232153X
EAN: 2147483647
Year: 2001
Pages: 204

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net