12.2 Thread States: Life Cycle of a Thread
At any time, a thread is said to be in one of several thread states (illustrated in Fig. 12.1)2. This section discusses these states and the transitions between states. Two classes critical for multithreaded applications are Thread and Monitor (System.Threading namespace). This section also discusses several methods of classes Thread and Monitor that cause state transitions.
A new thread begins its lifecyle in the Unstarted state. The thread remains in the Unstarted state until the program calls Thread method Start, which places the thread in the Started state (sometimes called the Ready or Runnable state) and immediately returns control to the calling thread. Then the thread that invoked Start, the newly Started thread and any other threads in the program execute concurrently.
Fig. 12.1 Thread life cycle.
The highest priority Started thread enters the Running state (i.e., begins executing) when the operating system assigns a processor to the thread (Section 12.3 discusses thread priorities). When a Started thread receives a processor for the first time and becomes a Running thread, the thread executes its ThreadStart delegate, which specifies the actions the thread will perform during its lifecyle. When a program creates a new Thread, the program specifies the Thread's ThreadStart delegate as the argument to the Thread constructor. The ThreadStart delegate must be a method that returns void and takes no arguments.
A Running thread enters the Stopped (or Dead) state when its ThreadStart delegate terminates. Note that a program can force a thread into the Stopped state by calling Thread method Abort on the appropriate Thread object. Method Abort throws a ThreadAbortException in the thread, normally causing the thread to terminate. When a thread is in the Stopped state and there are no references to the thread object, the garbage collector can remove the thread object from memory.
A thread enters the Blocked state when the thread issues an input/output request. The operating system blocks the thread from executing until the operating system can complete the I/O for which the thread is waiting. At that point, the thread returns to the Started state, so it can resume execution. A Blocked thread cannot use a processor even if one is available.
There are three ways in which a Running thread enters the WaitSleepJoin state. If a thread encounters code that it cannot execute yet (normally because a condition is not satisfied), the thread can call Monitor method Wait to enter the WaitSleepJoin state. Once in this state, a thread returns to the Started state when another thread invokes Monitor method Pulse or PulseAll. Method Pulse moves the next waiting thread back to the Started state. Method PulseAll moves all waiting threads back to the Started state.
A Running thread can call Thread method Sleep to enter the WaitSleepJoin state for a period of milliseconds specified as the argument to Sleep. A sleeping thread returns to the Started state when its designated sleep time expires. Sleeping threads cannot use a processor, even if one is available.
Any thread that enters the WaitSleepJoin state by calling Monitor method Wait or by calling Thread method Sleep also leaves the WaitSleepJoin state and returns to the Started state if the sleeping or waiting Thread's Interrupt method is called by another thread in the program.
If a thread cannot continue executing (we will call this the dependent thread) unless another thread terminates, the dependent thread calls the other thread's Join method to "join" the two threads. When two threads are "joined," the dependent thread leaves the WaitSleepJoin state when the other thread finishes execution (enters the Stopped state). If a Running Thread's Suspend method is called, the Running thread enters the Suspended state. A Suspended thread returns to the Started state when another thread in the program invokes the Suspended thread's Resume method.