Extending .NET Threads

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.


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, 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",  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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.17.64