Section 8.3. Working with Threads


8.3. Working with Threads

In .NET, a thread is the basic unit of execution. .NET threads are managed code representations of the underlying threads of the operating system. Under the current version of .NET on Windows, .NET threads map one-to-one to Win32 native threads. However, this mapping can be changed in other hosts. For example, SQL Server 2005 is a managed CLR host and is capable of using fibers for managed threads. Throughout this chapter, the underlying host is considered to be Windows unless otherwise specified. For each thread, the operating system allocates registers, a program counter, a stack, and a stack pointer, and assigns it a time slot and a priority. The operating system (presently) is the one responsible for thread scheduling and thread context switches, as well as thread-manipulation requests such as start and sleep. .NET exposes some of the native thread properties (such as priority). It also associates various managed-code properties with each thread, such as state, exception handlers, security principal (discussed in Chapter 12), name, unique ID, and culture (required for localization).

The .NET class Thread, defined in the System.Threading namespace, represents a managed thread. The Thread class provides various methods and properties to control the managed thread.

Calling the methods of the Thread class (be they static or instance methods) is always done on the stack of the calling thread, not on the stack of the thread represented by the Thread object. The one exception to this rule occurs when the calling thread calls methods on a Thread object that represents itself.


You can get hold of the thread on which your code is currently running by using the CurrentThread read-only static property of the Thread class:

     public sealed class Thread     {        public static Thread CurrentThread {get;}        //Other methods and properties     }

The CurrentThread property returns an instance of the THRead class. Each thread has a unique thread identification number, called a thread ID. You can access the thread ID via the ManagedThreadId property of the Thread class:

     using System.Threading;     Thread currentThread = Thread.CurrentThread;     int threadID = currentThread.ManagedThreadId;     Trace.WriteLine("Thread ID is "+ threadID);

THRead.ManagedThreadId is guaranteed to return a value that is unique process-wide. It's worth mentioning that the thread ID obtained by ManagedThreadId isn't related to the native thread ID allocated by the underlying operating system. You can verify that by opening Visual Studio's Threads debug window (Debug Windows) during a debugging session and examining the value in the ID column (see Figure 8-1). The ID column reflects the physical thread ID. The ManagedThreadId property simply returns a unique hash of the thread object. Having different IDs allows .NET threads in different hosting environments (such as Windows or SQL Server 2005) to map differently to the native operating support.

Figure 8-1. The Threads debug window


Another useful property of the Thread class is the Name string property. Name allows you to assign a human-readable name to a thread:

     using System.Threading;     Thread currentThread = Thread.CurrentThread;     string threadName = "Main UI Thread";     currentThread.Name = threadName;

Only you as the developer can assign the thread name, and by default, a new .NET thread is nameless. You can only set the thread's name once. Although naming a thread is optional, I highly recommend doing so because it's an important productivity feature. Windows doesn't have the ability to assign a name to a thread. In the past, when you debugged native Windows code, you had to record the new thread ID in every debugging session (using the Threads debug window). These IDs were not only confusing (especially when multiple threads were involved) but also changed in each new debugging session. Visual Studio's Threads debug window (see Figure 8-1) displays the value of the Name property, thus easing the task of tracing and debugging multithreaded applications. You can even set a breakpoint filter, instructing the debugger to break only when a thread with a specific name hits the breakpoint. Name is a good example of a managed-code property that .NET adds to native threads.

In Visual Studio 2005, breakpoint filters are disabled by default. To enable them, go to Tools Options Debugging General and check the "Enable breakpoint filters checkbox. Now, you can set a breakpoint filter by selecting Filter...from the breakpoint context menu.


8.3.1. Creating Threads

To spawn a new thread, you need to create a new Thread object and associate it with a method that is referred to as the thread method. The new THRead object executes the method on a separate thread. The thread terminates once the thread method returns. The thread method can be a static or an instance method, can be public or private, and can be called on your object or on another. The only requirement is that the thread method should have either one of these signatures:

     void <MethodName>(  );     void <MethodName>(object argument);

depending on whether or not you want to pass in an argument.

If you want to use an argument-less thread method, you associate a Thread object with the thread method by using a dedicated delegate called ThreadStart, defined as:

     public delegate void ThreadStart(  );

One of the THRead class constructors accepts as a single construction parameter an instance of the ThreadStart delegate, which targets the thread method:

     public sealed class Thread     {        public Thread(ThreadStart start);        //Other methods and properties     }

Once you've created a new Thread object, you must explicitly call its Start( ) method to have it actually execute the thread method. Example 8-1 demonstrates creating and using a new thread.

Example 8-1. Spinning off a new thread
 public class MyClass {    public void ShowMessage(  )    {       Thread currentThread = Thread.CurrentThread;       string caption = "Thread ID = ";       caption += currentThread.ManagedThreadId;       MessageBox.Show("ShowMessage runs on a new thread",caption);    } } MyClass obj = new MyClass(  ); ThreadStart threadStart = obj.ShowMessage; Thread workerThread = new Thread(threadStart); workerThread.Start(  );

Calling the Start( ) method is a non-blocking operation, meaning that control returns immediately to the client that started the thread, even though it may be some time before the new thread actually starts (depending on the operating system's internal threading management). As a result, after calling Start( ), don't make any assumptions in your code that the thread is actually running.

Although you should have only one thread method as a target for the ThreadStart delegate, you can associate it with multiple targets, in which case the new thread executes all methods in order, and the thread terminates once the last target method returns. However, there's little practical use for such a setting. In general, you should have only one target thread method. You can enforce that by passing the thread method name directly to the Thread constructor, instead of using a delegate variable:

     Thread workerThread = new Thread(obj.ShowMessage);

8.3.1.1 Designing thread methods

A thread method can do whatever you want it to, but typically it will contain a loop of some sort. In each loop iteration, the thread performs a finite amount of work and then checks some condition, which lets it know whether to perform another iteration or to terminate:

     public void MyThreadMethod(  )     {        while(<some condition>)        {           <Do some work>        }     }

: Threads and Exceptions

If the thread method completes its execution, .NET will shutdown the thread gracefully. However, any unhandled exception thrown on the call stack of the thread will terminate not just the thread but also will trigger shutdown of hosting process itself. The one exception to this rule is the ThreadAbortException (discussed later on), which will only terminate the aborted thread. This behavior is a breaking change introduced by .NET 2.0. In contract, .NET 1.1 will only terminate the thread that encountered the unhandled exception. When porting .NET 1.1 applications to .NET 2.0, make sure that your thread methods always catch and handle any exception, otherwise you risk unplanned application termination.


The condition is usually the result of some external event telling the thread that its work is doneit can be as simple as checking the value of a flag or waiting on a synchronization event. The condition is usually changed by another thread. Consequently, changing and verifying the condition must be done in a thread-safe manner, using threading synchronization objects, as explained later in this chapter.

8.3.2. Blocking Threads

The THRead class provides a number of methods you can use to block the execution of a thread, similar in their effect to the native mechanisms available to Windows programmers. These include suspending a thread, putting a thread to sleep, waiting for a thread to die. Developers often misuse these mechanisms without ever realizing they were doing anything wrong. This section outlines the various blocking options and discusses why it's a bad idea to use most of them.

8.3.2.1 Suspending and resuming a thread

The THRead class provides the Suspend( ) method, which suspends the execution of a thread, and the Resume( ) method, which resumes a suspended thread:

     public sealed class Thread     {        public void Resume(  );        public void Suspend(  );        //Other methods and properties     }

Anybody can call Suspend( ) on a Thread object, including objects running on that thread, and there is no harm in calling Suspend( ) on an already suspended thread. Obviously, only clients on other threads can resume a suspended thread. Suspend( ) is a non-blocking call, meaning that control returns immediately to the caller and the thread is suspended later, usually at the next safe point. A safe point is a point in the code where it's safe for garbage collection to take place. (Recall from Chapter 4 that when garbage collection occurs, .NET must suspend all running threads to compact the heap, move objects in memory, and patch client-side references.) The JIT compiler identifies those points in the code that are safe for suspending the thread (such as when returning from method calls or branching for another loop iteration). When Suspend( ) is called, the thread is suspended once it reaches the next safe point.

The bottom line is that suspending a thread isn't an instantaneous operation. The need to suspend and then resume a thread usually results from a need to synchronize the execution of that thread with other threads, but using Suspend( ) and Resume( ) for that purpose isn't recommended because there is no telling when these operations will take place. Consequently, .NET 2.0 applies the Obsolete attribute to Suspend( ) and Resume( ), warning you not to use them. If you need to suspend the execution of a thread and then resume it later, you should use the dedicated .NET synchronization objects (described later). The synchronization objects provide a deterministic way of blocking a thread or signaling it to continue executing. In general, you should avoid explicitly suspending and resuming threads.

8.3.2.2 Putting a thread to sleep

The Thread class provides two overloaded versions of the static Sleep( ) method, which puts a thread to sleep for a specified timeout:

     public sealed class Thread     {        public static void Sleep(int millisecondsTimeout);        public static void Sleep(TimeSpan timeout);        //Other methods and properties     }

Because Sleep( ) is a static method, you can put only your own thread to sleep:

     Thread.Sleep(20);//Sleep for 20 milliseconds

Sleep( ) is a blocking call, meaning that control returns to the calling thread only after the sleep period has elapsed. Sleep( ) puts the thread in a special queue of threads waiting to be awakened by the operating system. Any thread that calls Sleep( ) willingly relinquishes the remainder of its allocated CPU time slot, even if the sleep timeout is less than the remainder of the time slot. Consequently, calling Sleep( ) with a timeout of zero is a way to force a thread context switch:

     Thread.Sleep(0);//Forces a context switch

If no other thread with the same or higher priority is ready to run, control returns to the thread (thread priority is discussed later, in the section "Thread Priority and Scheduling").

You can also put a thread to sleep indefinitely, using the Infinite static constant of the Timeout class:

     Thread.Sleep(Timeout.Infinite);

Of course, putting a thread to sleep indefinitely is an inefficient use of the system services; it's better to simply terminate the thread (by returning from the thread method). If you need to block a thread until some event takes place, use .NET synchronization objects. In fact, you should generally avoid putting a thread to sleep, unless you specifically want the thread to act as a kind of timer. Traditionally, you put threads to sleep to cope with race conditions, by explicitly removing some of the threads involved in the race condition. A race condition is a situation in which thread T1 needs to have another thread, T2, complete a task or reach a certain state. The race condition occurs when T1 proceeds as if T2 is ready, when in fact it may not be. Sometimes T1 has its own processing to do, and that (in a poorly designed system) usually keeps it busy long enough to avoid the race condition. Occasionally, however, T1 will complete before T2 is ready, and an error will occur. Using Sleep( ) to resolve a race condition is inappropriate, because it doesn't address the root cause of the race condition (usually, the lack of proper synchronization between the participating threads). Putting threads to sleep is at best a makeshift solution, because the race condition can still manifest itself in different ways; also, it isn't likely to work when more threads get involved. Avoid putting a thread to sleep, and use .NET synchronization objects instead.

: A Minute for TimeSpan

Traditionally, most APIs in Windows that deal with time use some form of physical time measurement, such as seconds or milliseconds. You probably have no problem converting a minute or two to seconds. However, it's harder to convert 1 hour and 48 minutes into seconds, or 2 days. The TimeSpan struct addresses this issue by providing many methods for time conversion and representing time periods in a uniform manner. For example, if you need to represent 2 days, use the static method FromDays( ), which returns a TimeSpan value representing 2 days:

     TimeSpan TimeSpan = TimeSpan.FromDays(2);

Many of the methods discussed in this chapter that deal with blocking a thread have versions that accept TimeSpan instead of physical time units.


8.3.2.3 Spinning while waiting

The Thread class provides another sleep-like operation, called SpinWait( ):

     public static void SpinWait(int iterations);

When a thread calls SpinWait( ), the calling thread waits the number of iterations specified but is never added to the queue of waiting threads. As a result, the thread is effectively put to sleep without relinquishing the remainder of its CPU time slot. The .NET documentation doesn't define what an iteration is, but it's likely mapped to a predetermined number (probably just one) of no-operation (NOP) assembly instructions. Consequently, the following SpinWait( ) instruction will take a different amount of time to complete on machines with different CPU clock speeds:

     int long Million = 1000000;     Thread.SpinWait(Million);

SpinWait( ) isn't intended to replace Sleep( ), but rather is available as an advanced optimization technique. If you know that some resource your thread is waiting for will become available in the immediate future, it's potentially more efficient to spin and wait than it would be to use either Sleep( ) or a synchronization object, because these force a thread context switch, which is one of the most expensive operations performed by the operating system. However, even in the esoteric cases for which SpinWait( ) was designed, using it amounts to an educated guess at best. SpinWait( ) gains you nothing if the resource isn't available at the end of the call, or if the operating system preempts your thread because its time slot has elapsed or because another thread with a higher priority is ready to run. In general, I recommend that you always use deterministic programming (synchronization objects, in this case) and avoid optimization techniques.

8.3.2.4 Joining a thread

The Thread class's Join( ) method allows one thread to wait for another thread to terminate. Any client that has a reference to a THRead object can call Join( ) and have the client thread blocked until the thread terminates:

     static void WaitForThreadToDie(Thread thread)     {        thread.Join(  );     }

Join( ) returns regardless of the cause of deatheither natural (the thread returns from the thread method) or unnatural (the thread encounters an exception).

Note that it is imperative to always check before calling Join( ) that you are not joining your own thread:

     static void WaitForThreadToDie(Thread thread)     {        Debug.Assert(Thread.CurrentThread.ManagedThreadId !=                     thread.ManagedThreadId);        thread.Join(  );     }

Doing so will prevent a deadlock of waiting for your own thread to die. Join( ) is useful when dealing with application shutdown; when an application starts its shutdown procedure, it typically signals all the worker threads to terminate and then waits for the threads to terminate before proceeding with the shutdown. The standard way of doing this is to call Join( ) on the worker threads. Calling Join( ) is similar to waiting on a thread handle in the Win32 world, and it's likely that the Join( ) method implementation does just that.

The Join( ) method has two overloaded versions, allowing you to specify a waiting timeout:

     public sealed class Thread     {        public void Join(  );        public bool Join(int millisecondsTimeout);        public bool Join(TimeSpan timeout);        //Other methods and properties     }

When you specify a timeout, Join( ) returns when the timeout has expired or when the thread is terminated, whichever happens first. The bool return value is set to false if the timeout has elapsed but the thread is still running, and to true if the thread is dead.

8.3.2.5 Interrupting a waiting thread

You can rudely awaken a sleeping or waiting thread by calling the Interrupt( ) method of the Thread class:

     public void Interrupt(  );

Calling Interrupt( ) unblocks a sleeping thread (or a waiting thread, such as a thread that called Join( ) on another thread) and throws an exception of type ThreadInterruptedException in the unblocked thread. If the code the thread executes doesn't catch that exception, the thread is terminated by the runtime.

If a call to THRead.Interrupt( ) is made on a thread that isn't sleeping or waiting, the next time the thread tries to go to sleep or wait .NET immediately throws an exception of type ThreadInterruptedException in its call stack. Note, however, that calling Interrupt( ) doesn't interrupt a thread that is executing unmanaged code via interop; nor does it interrupt a thread that is in the middle of a call to SpinWait( ), because as far as the operating system is concerned that thread is not actually waiting at all.

Again, you should avoid relying on drastic solutions such as throwing exceptions to unblock another thread. Use .NET synchronization objects instead, to gain the benefits of structured and deterministic code flow.

8.3.3. Aborting a Thread

The Thread class provides an Abort( ) method, which can forcefully try to terminate a .NET thread. Calling Abort( ) throws an exception of type THReadAbortException in the thread being aborted. ThreadAbortException is a special kind of exception. Even if the thread method uses exception handling to catch exceptions, as in the following example code:

     public void MyThreadMethod(  )     {        try        {           while(<some condition>)           {              <Do some work>           }        }        catch        {           //Handle exceptions here        }     }

after the catch statement is executed, .NET re-throws the THReadAbortException to terminate the thread. This is done so that non-structured attempts that ignore the abort by jumping to the beginning of the thread method simply don't work:

     //Code that doesn't work when ThreadAbortException is thrown     public void MyThreadMethod(  )     {        Resurrection:        try        {           while(<some condition>)           {              <Do some work>           }        }        catch        {           goto Resurrection;        }     }

Using non-structured goto instructions is strongly discouraged in any case. Never use goto, except to fall through in a C# switch statement.


The Abort( ) method has two overloaded versions:

     public sealed class Thread     {        public void Abort(  );        public void Abort(object stateInfo)        //Other methods and properties     }

One version allows the party that calls Abort( ) to provide a generic parameter of type object called stateInfo. stateInfo can convey application-specific information to the aborted thread, such as why it's being aborted. The aborted thread can access the stateInfo object via the ExceptionState public property of the THReadAbortException class, if the thread is using exception handling.

Example 8-2 demonstrates using Abort( ) to terminate a thread. The example creates a new thread, whose thread method simply traces an incrementing integer to the Output window. The thread method uses exception handling, and it traces to the Output window the information passed to it using the stateInfo parameter of Abort( ). Note that the thread that called Abort( ) uses Join( ) to wait for the thread to die. This is the recommended practice, because the thread can perform an open-ended number of operations in its catch and finally exception-handling statements.

Example 8-2. Terminating a thread using Abort( )
 public class MyClass {      public void DoWork(  )      {       try       {          int i = 0;          while(true)          {             Trace.WriteLine(i++);          }       }       catch(ThreadAbortException exception)       {          string cause;          cause = (string)exception.ExceptionState;          Trace.WriteLine(cause);       }    } } MyClass obj = new MyClass(  ); Thread workerThread = new Thread(obj.DoWork); workerThread.Start(  ); /* Do some work, then: */ workerThread.Abort("Time to go"); workerThread.Join(  );

If Abort( ) is called before the thread is started, .NET doesn't start the thread when Thread.Start( ) is called. If THRead.Abort( ) is called while the thread is blocked (either by calling Sleep( ) or Join( ), or if the thread is waiting on one of the .NET synchronization objects), .NET unblocks the thread and throws a ThreadAbortException in it. However, you can't call Abort( ) on a suspended thread. Doing so results in an exception of type THReadStateException on the calling side, with the error message "Thread is suspended; attempting to abort." .NET then terminates the suspended thread without letting it handle the exception.

The Thread class also has an interesting counter-abort methodthe static ResetAbort( ) method:

     public static void ResetAbort(  );

Calling Thread.ResetAbort( ) in a catch statement prevents .NET from re-throwing a ThreadAbortException at the end of the catch statement:

     catch(ThreadAbortException exception)     {        Trace.WriteLine("Refusing to die");        Thread.ResetAbort(  );        //Do more processing or even goto somewhere     }

ResetAbort( ) requires the ControlThread security permission. Permissions are explained in Chapter 12.


Terminating a thread by calling Abort( ) isn't recommended, for a number of reasons. The first is that it forces the thread to perform an ungraceful exit. Often, the thread needs to release resources it holds and perform some sort of cleanup before terminating. You can, of course, handle exceptions and put the cleanup code in the finally method, but you typically want to handle unexpected errors that way and not use it as the standard way to terminate a thread. Second, nothing prevents the thread from abusing .NET and either performing as many operations as it likes in the catch statement, jumping to a label, or calling ResetAbort( ). If you want to terminate a thread, you should do so in a structured manner, using the .NET synchronization objects. You should signal the thread method to exit by using a member variable or event. Later, after a discussion of manual synchronization, this chapter presents a template for terminating threads using this technique, without resorting to Abort( ).

Calling Thread.Abort( ) has another liability: if the thread makes an interop call (using COM interop or P-Invoke), the interop call may take a while to complete. If Thread.Abort( ) is called during the interop call, .NET doesn't abort the thread; it lets the thread complete the interop call, only to abort it when it returns. This is another reason why Thread.Abort( ) isn't guaranteed to succeed (or succeed immediately).


8.3.4. Thread States

.NET manages a state machine for each thread and moves the threads between states. The ThreadState enum defines the set of states a .NET managed thread can be in:

     [Flags]     public enum ThreadState     {        Aborted          = 0x00000100,        AbortRequested   = 0x00000080,        Background       = 0x00000004,        Running          = 0x00000000,        Stopped          = 0x00000010,        StopRequested    = 0x00000001,        Suspended        = 0x00000040,        SuspendRequested = 0x00000002,        Unstarted        = 0x00000008,        WaitSleepJoin    = 0x00000020     }

For example, if a thread is in the middle of a Sleep( ), Join( ), or wait call on one of the synchronization objects, the thread is in the THReadState.WaitSleepJoin state. .NET throws an exception of type ThreadStateException when it tries to move the thread to an inconsistent statefor example, by calling Start( ) on a thread at the THReadState.Running state or trying to abort a suspended thread (THReadState.Suspended). The Thread class has a public read-only property called ThreadState that you can access to find the exact state of a thread:

     public ThreadState ThreadState{get;}

The ThreadState enum values can be bit-masked together, so testing for a given state is typically done as follows:

     Thread workerThread;     //Some code to initialize workerThread, then:     ThreadState state = workerThread.ThreadState;     if((state & ThreadState.Unstarted) == ThreadState.Unstarted)     {        workerThread.Start(  );     }

However, by the time you retrieve the thread's state and decide to act upon it, the state may already have changed. I don't recommend ever designing your application so that you rely on the information provided by the ThreadState property; rather, you should design so that your code doesn't depend on the thread being in a particular state. If your thread transitions between logical states specific to your application, such as beginning or finishing tasks, use .NET synchronization objects to synchronize transitioning between those states.

The only time you might need to rely on state information is to check whether the thread is alive, which is required sometimes for diagnostics or control flow. Even then, you should use the Boolean read-only public property IsAlive instead of the ThreadState property:

     public bool IsAlive { get; }

For example, there is little point in calling Join( ) on a thread if the thread isn't alive:

     Thread workerThread;     //Some code to start workerThread, then:     if(workerThread.IsAlive)     {        workerThread.Join(  );     }     Trace.WriteLine("Thread is dead");

8.3.5. Foreground and Background Threads

.NET defines two kinds of managed threads: background and foreground. The two thread types are exactly the same, except that .NET keeps the process alive as long as there is at least one foreground thread running, whereas a background thread doesn't keep the .NET process alive once all foreground threads have exited.

New threads are created as foreground threads by default. To mark a thread as a background thread, you need to set the Thread object's IsBackground property to true:

     public bool IsBackground { get; set; }

When the last foreground thread in a .NET application (actually, in an application domain, discussed in the section "Synchronizing Threads") terminates, .NET shuts down the application. The .NET runtime then tries to terminate all the remaining background threads by throwing a ThreadAbortException in each. Background threads are a poor man's solution for application shutdown: instead of designing the application correctly to keep track of what threads it created (and which threads are still running and need to be terminated when the application shuts down), a quick and dirty solution is to let .NET try to terminate all the background threads for you. Normally, you shouldn't count on .NET to kill your background threads for you. You should have a deterministic, structured way of shutting down your applicationin other words, you should do your own bookkeeping and explicitly control the lifecycles of each of your threads, taking steps to shut down all threads on exit.

8.3.6. Thread Priority and Scheduling

Each thread is allocated a fixed time slot to run on the CPU and assigned a priority. In addition, each thread is either ready to run or waiting for some event to occur, such as a synchronization object being signaled or a sleep timeout elapsing. The underlying operating system schedules for execution those threads that are ready to run based on the threads' priorities. Thread scheduling is preemptive, meaning that the thread with the highest priority always gets to run. If a thread T1 with priority P1 is running, and suddenly thread T2 with priority P2 is ready to run, and P2 is greater than P1, the operating system will preempt (pause) T1 and allow T2 to run. If multiple threads with the same (highest) priority are ready to run, the operating system will let each run for the duration of its CPU time slot and then preempt it in favor of another thread with the same priority, in a round-robin fashion.

The Thread class provides the Priority property of the enum type ThreadPriority, which allows you to retrieve or set the thread priority:

     public ThreadPriority Priority { get; set; }

The enum ThreadPriority provides five priority levels:

     public enum ThreadPriority     {        Lowest,        BelowNormal,        Normal,        AboveNormal,        Highest     }

New .NET threads are created by default with a priority of ThreadPriority.Normal. Developers often abuse thread-priority settings as a way to control the flow of a multithreaded application, to work around race conditions. Tinkering with thread priorities generally isn't an appropriate solution, though, and it can lead to some adverse side effects and other race conditions. For example, say you have two threads that are involved in a race condition. By increasing one thread's priority in the hope that it will preempt the other and thus win the race, you often just decrease the probability of the race condition occurring (rather than eliminating it altogether), because the thread with the higher priority can still be switched out or blocked. In addition, you must consider whether it makes sense to always run that thread at a higher priority. Granting it a high priority could paralyze other aspects of your application, because it won't only preempt the thread with which you're trying to avoid the race condition. You could, of course, increase the priority only temporarily, but then you would address just that particular occurrence of the race condition and remain exposed to future occurrences.

You may be tempted to always keep that thread at a high priority and also increase the priorities of other affected threads, but this is also problematic. Often, increasing one thread's priority causes an inflation of thread priorities all around, because the normal balance and time-sharing governed by the operating system is disturbed. The result can be a set of threads, all with the highest priority, still involved in race conditions. The major adverse effect now is that .NET itself suffers, because many of its internal threads (such as threads used to manage memory, execute remote calls, and so on) are suddenly competing with your high-priority threads.

A further complication when manipulating priority settings is that preemptive operating systems (such as Windows) may dynamically change threads' priorities to resolve priority-inversion situations. A priority inversion occurs when a thread with a lower priority runs instead of a thread with a higher priority. Because .NET threads are currently mapped to the underlying Windows threads, these dynamic changes propagate to the managed threads as well. Consider, for example, three managed threads, T1, T2, and T3, with respective priorities of ThreadPriority.Lowest, THReadPriority.Normal, and THReadPriority.Highest. T3 is waiting for a resource held by T1. T1 is ready to run to release the resource, except that T2 is now running, preventing T1 from executing. As a result, T2 prevents T3 from running, and priority inversion takes place because T3 has a priority greater than that of T2.

To cope with priority inversions, the operating system not only keeps track of thread priorities but also maintains a scoreboard showing who got to run and how often. If a thread is denied the CPU for a long time (a few seconds), the operating system dynamically boosts that thread's priority to a higher priority, lets it run for a couple of time slots with the new priority, and then resets the priority back to its original value. In the previous scenario, this allows T1 to run, release the resource T3 is waiting for, and then regain its original priority. Once the resource is available, T3 will be ready to run and will preempt T2.

The point of this example and the earlier arguments is that you should avoid trying to control the application flow by setting thread priorities. Instead, use .NET synchronization objects to control and coordinate the flow of your application and to resolve race conditions. Set threads' priorities to values other than ThreadPriority.Normal only when the semantics of the application require it. For example, if you develop a screen saver, its threads should run at priority ThreadPriority.Lowest so that other background operations (such as compilation, network access, or number crunching) can take place without being affected by the screen saver.



Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

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