Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Working with .NET Threads : Page 3

This article describes the dos and don'ts of the Thread class, and presents a wrapper class that simplifies starting a thread, correctly terminates a thread, and offers a more consistent class interface than that of the raw Thread class.


advertisement
Passing Thread Parameters
The constructor for the Thread class does not accept any parameters except the ThreadStart delegate, and the thread method itself takes no parameters. If you want to pass parameters to the thread method, you need to set properties on the class that provides the thread method, or have some other custom way, such as having the method retrieve its parameters from a known location planned in advance.

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, and waiting for a thread to die.

Suspending and Resuming a Thread
The Thread class provides the Suspend() method, used to suspend the execution of a thread, and the Resume() method, used to resume 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 safe for garbage collection. When garbage collection takes place, .NET must suspend all running threads, so that it can 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 returning from method calls or branching for another loop iteration). When Suspend() is called, the thread will be suspended once it reaches the next safe point.

Avoid explicitly suspending or resuming threads.
The bottom line is that suspending a thread is not 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. Using Suspend() and Resume() for that purpose is not recommended, because there is no telling when it will take place. If you need to suspend the execution of a thread, and then later resume it, you should use the dedicated .NET synchronization objects. The synchronization objects provide a deterministic way of blocking a thread or signaling it to continue executing. In general, avoid explicitly suspending or resuming threads.

Putting a Thread to Sleep
The Threadclass provides two overloaded versions of the static Sleep() method, used to put 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 only put 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() is willingly relinquishing the remainder of its allocated CPU time slot, even if the sleep timeout is less than the reminder of the time slot. Consequently, calling Sleep() with a timeout of zero is a way of forcing a thread context switch:

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

If no other thread (with this priority or higher) is ready to run, control will return to the thread.

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, because it would be 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, in general you should avoid putting a thread to sleep unless you specifically want the thread to act as a kind of timer. Traditionally, developers resorted to putting a thread 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 where thread T1 needs to have another thread (T2) complete a task or reach a certain state. The race condition occurs when the T1 proceeds as if the T2 is ready, while in fact it may not be.

Avoid putting a thread to sleep; use .NET synchronization objects instead.
Sometimes the T1 has its own processing to do, and that (in a poorly designed system) will usually keep it busy enough to avoid the race condition. Occasionally, however, the T1 will complete before T2 is ready, and an error will occur. Using Sleep() to resolve a race condition is inappropriate because it does not address the root cause of the race condition, usually, lack of proper synchronization in the first place between the participating threads. In that case, putting threads to sleep is at best a makeshift solution because the race condition could still manifest itself in different ways, and it is not likely to work when more threads get involved. Avoid putting a thread to sleep, and use .NET synchronization objects instead.

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, and the thread 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 does not define what an iteration is, but it is likely mapped to a predetermined number (probably just one) of NOP (no-operations) assembly instructions. Consequently, the following SpinWait() instruction will take different time to complete on machines with different CPU clock speeds:

const long MILLION = 1000000; Thread.SpinWait(MILLION);

SpinWait() is not intended to replace Sleep(), but is rather made 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 is potentially more efficient to spin and wait, instead of using either Sleep() or a synchronization object, because those force a thread context switch, which is one of the most expensive operations performed by the operating system. Even in the esoteric cases for which SpinWait() was designed, using it is an educated guess at best. SpinWait() will gain you nothing if the resource is not 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 should always use deterministic programming (using synchronization objects in this case) and avoid optimization techniques.

Joining a Thread
The Thread class provides the Join() method, which 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. Note that you should always check before calling Join() that the thread you are trying to join to is not your current thread:

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

Join() will return regardless of the cause of death—either natural (the thread returns from the thread method) or unnatural (the thread encountered an exception). 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 the application waits for the threads to terminate. The standard way of doing that is by calling Join() on the worker threads.

Calling Join() is similar to waiting on a thread handle in the Win32 world, and it is likely the Join() method implementation does just that. Unfortunately, the Threadclass does not provide a WaitHandle to be used in conjunction with multiple wait operations. This renders Join() inferior to raw Windows programming, because when waiting for multiple events to occur, you want to dispatch the wait request as one atomic operation to reduce the likelihood of deadlocks.

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() will return when the timeout has expired or when the thread is terminated, whichever happens first. The bool return value will be set to false if the timeout has elapsed but the thread is still running, and to true if the thread is dead.



Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap
Thanks for your registration, follow us on our social networks to keep up-to-date