Creating and Executing Threads

Figure 15.3 demonstrates basic threading techniques, including constructing THRead objects and the use of the Thread class's static method Sleep. The program creates three threads of execution, each with the default priority Normal. Each thread displays a message indicating that it is going to sleep for a random interval of from 0 to 5000 milliseconds, then goes to sleep. When each thread awakens, the thread displays its name, indicates that it is done sleeping, terminates and enters the Stopped state. You will see that method Main (i.e., the Main thread of execution) terminates before the application terminates. The program consists of two classesThreadTester (lines 735), which creates the three threads, and MessagePrinter (lines 3864), which defines a Print method containing the actions each thread will perform.

Figure 15.3. Threads sleeping and printing.

1 // Fig. 15.3: ThreadTester.cs 2 // Multiple threads printing at different intervals. 3 using System; 4 using System.Threading; 5 6 // class ThreadTester demonstrates basic threading concepts 7 class ThreadTester 8 { 9 static void Main( string[] args ) 10 { 11 // Create and name each thread. Use MessagePrinter's 12 // Print method as argument to ThreadStart delegate. 13 MessagePrinter printer1 = new MessagePrinter(); 14 Thread thread1 = new Thread ( new ThreadStart( printer1.Print ) ); 15 thread1.Name = "thread1"; 16 17 MessagePrinter printer2 = new MessagePrinter(); 18 Thread thread2 = new Thread ( new ThreadStart( printer2.Print ) ); 19 thread2.Name = "thread2"; 20 21 MessagePrinter printer3 = new MessagePrinter(); 22 Thread thread3 = new Thread ( new ThreadStart( printer3.Print ) ); 23 thread3.Name = "thread3"; 24 25 Console.WriteLine( "Starting threads" ); 26 27 // call each thread's Start method to place each 28 // thread in Running state 29 thread1.Start(); 30 thread2.Start(); 31 thread3.Start(); 32 33 Console.WriteLine( "Threads started " ); 34 } // end method Main 35 } // end class ThreadTester 36 37 // Print method of this class used to control threads 38 class MessagePrinter 39 { 40 private int sleepTime; 41 private static Random random = new Random(); 42 43 // constructor to initialize a MessagePrinter object 44 public MessagePrinter() 45 { 46 // pick random sleep time between 0 and 5 seconds 47 sleepTime = random.Next( 5001 ); // 5001 milliseconds 48 } // end constructor 49 50 // method Print controls thread that prints messages 51 public void Print() 52 { 53 // obtain reference to currently executing thread 54 Thread current = Thread.CurrentThread; 55 56 // put thread to sleep for sleepTime amount of time 57 Console.WriteLine( "{0} going to sleep for {1} milliseconds", 58 current.Name, sleepTime ); 59 Thread.Sleep( sleepTime ); // sleep for sleepTime milliseconds 60 61 // print thread name 62 Console.WriteLine( "{0} done sleeping", current.Name ); 63 } // end method Print 64 } // end class MessagePrinter

Starting threads thread1 going to sleep for 1603 milliseconds thread2 going to sleep for 2355 milliseconds thread3 going to sleep for 285 milliseconds Threads started thread3 done sleeping thread1 done sleeping thread2 done sleeping

 

Starting threads thread1 going to sleep for 4245 milliseconds thread2 going to sleep for 1466 milliseconds Threads started thread3 going to sleep for 1929 milliseconds thread2 done sleeping thread3 done sleeping thread1 done sleeping

Objects of class MessagePrinter (lines 3864) control the life cycle of each of the three threads created in class ThreadTester's Main method. Class MessagePrinter consists of instance variable sleepTime (line 40), static variable random (line 41), a constructor (lines 4448) and a Print method (lines 5163). Variable sleepTime stores a random integer value chosen when a new MessagePrinter object's constructor is called. Each thread controlled by a MessagePrinter object sleeps for the amount of time specified by the corresponding MessagePrinter object's sleepTime.

The MessagePrinter constructor (lines 4448) initializes sleepTime to a random number of milliseconds from 0 up to, but not including, 5001 (i.e., from 0 to 5000).

Method Print begins by obtaining a reference to the currently executing thread (line 54) via class Thread's static property CurrentThread. The currently executing thread is the one that invokes method Print. Next, lines 5758 display a message indicating the name of the currently executing thread and stating that the thread is going to sleep for a certain number of milliseconds. Note that line 58 uses the currently executing thread's Name property to obtain the thread's name (set in method Main when each thread is created). Line 59 invokes static Thread method Sleep to place the thread in the WaitSleepJoin state. At this point, the thread loses the processor, and the system allows another thread to execute if one is ready to run. When the thread awakens, it re-enters the Running state and waits to be assigned a processor by the thread scheduler. When the MessagePrinter object enters the Running state again, line 62 outputs the thread's name in a message that indicates the thread is done sleeping, and method Print terminates.

Class ThreadTester's Main method (lines 934) creates three objects of class MessagePrinter, at lines 13, 17 and 21, respectively. Lines 14, 18 and 22 create and initialize three THRead objects. Note that each Thread's constructor receives a ThreadStart delegate as an argument. A ThreadStart delegate represents a method with no arguments and a void return type that specifies the actions a thread will perform. Line 14 initializes the THReadStart delegate for tHRead1 with printer1's Print method. When tHRead1 enters the Running state for the first time, tHRead1 will invoke printer1's Print method to perform the tasks specified in method Print's body. Thus, thread1 will print its name, display the amount of time for which it will go to sleep, sleep for that amount of time, wake up and display a message indicating that the thread is done sleeping. At that point, method Print will terminate. A thread completes its task when the method specified by its THReadStart delegate terminates, at which point the thread enters the Stopped state. When tHRead2 and thread3 enter the Running state for the first time, they invoke the Print methods of printer2 and printer3, respectively. thread2 and tHRead3 perform the same tasks as thread1 by executing the Print methods of the objects to which printer2 and printer3 refer (each of which has its own randomly chosen sleep time). Lines 15, 19 and 23 set each THRead's Name property, which we use for output purposes.

Error Prevention Tip 15 1

Naming threads helps in the debugging of a multithreaded program. Visual Studio .NET's debugger provides a Threads window that displays the name of each thread and enables you to view the execution of any thread in the program.

Lines 2931 invoke each Thread's Start method to place the threads in the Running state. Method Start returns immediately from each invocation, then line 33 outputs a message indicating that the threads were started, and the Main thread of execution terminates. The program itself does not terminate, however, because there are still non-background threads that are alive (i.e., the threads are Running and have not yet reached the Stopped state). The program will not terminate until its last non-background thread dies. When the system assigns a processor to a thread, the thread enters the Running state and calls the method specified by the thread's ThreadStart delegate. In this program, each thread invokes method Print of the appropriate MessagePrinter object to perform the tasks discussed previously.

Note that the sample outputs for this program show each thread and the thread's sleep time in milliseconds as the thread goes to sleep. The thread with the shortest sleep time normally awakens first, then indicates that it is done sleeping and terminates. In Section 15.8, we discuss multithreading issues that could prevent the thread with the shortest sleep time from awakening first (none of this is guaranteed). Notice in the second sample output that thread1 and thread2 were able to report their sleep times before Main could output its final message. This means that the main thread's quantum ended before it could finish executing Main, and thread1 and thread2 each got a chance to execute.

Категории