he .NET class Thread defined in the System.Threading namespace represents a managed thread. The Thread class offers raw control over the underlying operating system thread. However, with power comes liability, and usually most of the Thread class methods and properties should not be used by application developers. The article explains the rational behind its recommendations, and presents a higher-level wrapper class around the Thread type. The wrapper class provides only the safe methods, as well as compensates for basic deficiencies in the Thread class design.
Managing Thread ID
You can get hold of the current thread your code runs on 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 thread ID. You can access the thread ID via the GetHashCode() method of the Thread class:
Thread currentThread = Thread.CurrentThread; int threadID = currentThread.GetHashCode(); Trace.WriteLine("Thread ID is "+ threadID);
Thread.GetHashCode() is guaranteed to return a value that is unique process-wide. It is worth mentioning that the thread ID obtained by GetHashCode() is unrelated to the native thread ID allocated by the underlying operating system. You can verify that by opening the Thread’s debug window (under Debug|Windows) during a debug session, and examining the value of the ID column (see Figure 1). The ID column reflects the physical thread ID. Having different IDs allows for .NET threads in the future to map differently to the native operating system support. If you need to programmatically access the physical thread ID your code runs on, use the static method GetCurrentThreadId() of the AppDomain class:
int physicalID = AppDomain.GetCurrentThreadId(); int hashedID = Thread.CurrentThread.GetHashCode(); Debug.Assert(physicalID != hashedID);
Another useful property of the Thread class is the Name string property. Name allows you to assign a human-readable name to a thread:
Thread currentThread = Thread.CurrentThread; string threadName = "Main UI Thread"; currentThread.Name = threadName;
The thread name can only be assigned by the developer, and by default, a new .NET thread is nameless. Although naming a thread is optional, I highly recommend doing so, because it is an important productivity feature. Windows does not have the ability to assign a name to a thread. In the past, when developers debugged native Windows code, they 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. The Thread’s Debug window of Visual Studio .NET (see Figure 1) displays the value of the Name property, thus easing the task of tracing and debugging multithreaded applications. In addition, when a named thread terminates, VS .NET will automatically trace to the Output window the name of that thread, as part of the thread’s Exit method.
![]() |
Creating Threads To spawn a new thread, you need to create a new Thread object, and associate it with a method, called 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 either be a static or an instance method, a public or a private one, on your object or on another. The only requirement is that the thread method has this exact signature:
That is, no parameters and a void return type. You associate a Thread object with the thread method using a dedicated delegate called ThreadStart, defined as:
The Thread class constructor accepts as a single construction parameter an instance of the ThreadStart delegate, targeting the thread method:
Once you create a new thread object, you must explicitly call its Start() method to have it actually execute the thread method. Listing 1 demonstrates creating and using a new thread. 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 later (depending on the operating system internal threading management) until the new thread actually starts. As a result, do not make any assumptions in your code (after calling Start()) that the thread is actually running. Although you should only have one thread method as a target for the ThreadStart delegate, you could associate it with multiple targets, in which case the new thread will execute all the methods in order, and the thread will terminate once the last target method returns. However, there is little practical use for such a setting. In general, you should only have one target thread method. Designing Thread Methods
The condition is usually the result of some external event telling the thread that its work is done. The condition can be as simple as checking the value of a flag, to waiting on a synchronization event. The condition is usually changed by another thread. As a result, changing and verifying the condition must be done in a thread-safe manner, using threading synchronization objects. Passing Thread Parameters Blocking Threads Suspending and Resuming a Thread
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.
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
Because Sleep() is a static method, you can only put your own thread to sleep:
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:
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:
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.
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
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:
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
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:
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. Interrupting a Waiting Thread
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 does not catch that exception, then the thread is terminated by the runtime. If the thread is not sleeping (or waiting), and a call to Thread.Interrupt() is made, then the next time the thread tries to go to sleep (or wait), then .NET will immediately throw in its call stack the exception of type ThreadInterruptedException. 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. In addition, calling Interrupt() does not interrupt a thread that is executing unmanaged code via interop. Note that calling Interrupt() does not interrupt a thread that is in the middle of a call to SpinWait(), because that thread is actually not waiting at all (as far as the operation system is concerned). Aborting a Thread
After the catch statement is executed, .NET re-throws the ThreadAbortException to terminate the thread. This is done so that non-structured attempts to ignore the abort by jumping to the beginning of the thread method will simply not work:
If Abort() is called before the thread is started, .NET will never start the thread once 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 ThreadAbortException in it. However, you cannot call Abort() on a suspended thread. Doing so will result on the calling side with an exception of type ThreadStateException, with the error message “Thread is suspended; attempting to abort.” In addition, .NET will terminate the suspended thread without letting it handle the exception. The Thread class has an interesting counter-abort method?the static ResetAbort() method:
Calling Thread.ResetAbort() in a catch statement prevents .NET from re-throwing ThreadAbortException at the end of the catch statement:
Note that ResetAbort() demands the ControlThread security permission. Terminating a thread by calling Abort() is not recommended for a number of reasons. The first is that it forces the thread to perform an ungraceful exit. Often the thread will need to release resources it holds and perform some sort of a cleanup before terminating. You can of course handle exceptions, and put the cleanup code in the finally method, but you typically want to handle the unexpected errors that way, and not use it as the standard way of terminating your thread. Never use exception to control the normal flow of your application?it is akin to non-structured programming using goto. Second, nothing prevents the thread from abusing .NET and either performing as many operations as it likes in the catch statement or 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 using a member variable or event. 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 will not abort the thread, but lets it complete the interop call, only to abort it when it returns. This is yet another reason why Thread.Abort() is not guaranteed to succeed, or succeed immediately. Thread States
For example, if a thread is in the middle of a Sleep(), Join(), or a wait call on one of the synchronization objects, then the thread will be in the ThreadState.WaitSleepJoin state. .NET will throw an exception of type ThreadStateException when it tries to move the thread to an inconsistent state, such as 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 you can access to find out the exact state of the thread:
The ThreadState enum values can be bit-masked together, so testing for a given state is done typically as follows:
However, I don’t recommend ever designing your application so that you rely on the information provided by the ThreadState property. You should design so that your code does not depend on the thread being in a particular state. In addition, by the time you retrieve the thread’s state and decide to act upon it, the state may have changed. 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 exception to this is the knowledge that the thread is alive, required sometimes for diagnostics or control flow. For that reason, the Thread class has the Boolean read-only public property IsAlive that you should use instead of the ThreadState property:
For example, there is little point in calling Join() on a thread if the thread is not alive:
Therefore, in general, you should avoid accessing Thread.ThreadState. Foreground and Background Threads New threads are created as foreground by default. To mark a thread as a background thread, you need to set the Thread object’s IsBackground property to true:
When the last foreground thread in the application terminates, .NET shuts down the application. The .NET runtime tries to terminate all the remaining background threads by throwing ThreadAbortException in them. 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, 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 those background threads. Normally, you should not count on .NET to kill your background threads for you. You should have a deterministic, structured way of shutting down your application, by doing your own bookkeeping and explicitly controlling the life cycle of your threads and taking steps to shut down all threads on exit. Thread Priority and Scheduling The Thread class provides the Priority property of the enumeration type ThreadPriority, which allows you to retrieve or set the thread priority:
The enum ThreadPriority provides five priority levels:
New .NET threads are created by default with a priority of ThreadPriority.Normal. Developers often abuse thread priority settings as a means to control the flow of a multithreaded application to work around race conditions. Tinkering with thread priorities generally is not an appropriate solution and can lead to some adverse side effects and other race conditions. For example, imagine two threads that are involved in a race condition. By increasing one thread’s priority, in the hope that it will run at the expense of the other and win the race, you often just decrease the probability of the race condition, because the thread with the higher priority can still be switched out or perform blocking operations. In addition, does it make sense to always run that thread at a higher priority? That could paralyze other aspects of your application. 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 increase the priority of other affected threads. Often, increasing one thread’s priority causes an inflation of increased 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 with race conditions. The major adverse effect now is that .NET 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 threads. In addition, preemptive operating systems (like Windows) will dynamically change threads’ priorities to resolve priority inversions situations. A priority inversion occurs when threads with lower priority run instead of threads 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, 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, always 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, letting it run for a couple of time slots with the new priority, and then sets 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. T3 will be ready to run (because the resource is now available) and will preempt T2. The point of this example and the other arguments is that you should avoid controlling the application flow by setting thread priorities. 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 normal only when the semantics of the application requires 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 could take place, and not be affected by the screen saver. The WorkerThread Wrapper Class
WorkerThread provides easy thread creation and other features, including a Kill() method for terminating the thread instead of using Abort(). The potentially dangerous methods of the Thread class are not present in the interface of WorkerThread, and the good ones are maintained. Listing 2 shows the implementation of WorkerThread. Because the Thread class is sealed, I had to use containment rather than derivation when defining WorkerThread. WorkerThread has the m_ThreadObj member variable of type Thread, representing the underlying wrapped thread. You can access the underlying thread via the Thread property of WorkerThread, if you still want direct thread manipulation. Creating a New Thread
If autoStart is false, or when using the default constructor, you need to call the WorkerThread.Start() method, just like when using the raw Thread class:
The thread method for WorkerThread is the private Run() method. In Listing 2, all Run() does is trace the value of a counter to the Output window. WorkerThread provides the Name property for naming the underlying thread. Joining a Thread and the Thread Handle WorkerThread provides access to m_ThreadHandle via the Handle property. WorkerThread also provides the Boolean property, IsAlive, which not only calls the underlying thread’s IsAlive property, it also asserts that the m_ThreadHandle state is consistent: you can test whether a ManualResetEvent object is signaled by waiting on it for a timeout of zero, and checking the retuned value from the Wait() method. Killing Threads
Before every loop iteration, Run() checks the Boolean property EndLoop. If EndLoop is set to false, Run() performs another iteration. WorkerThread provides the Kill() method, which sets the EndLoop to true, causing Run() to return and the thread to terminate. EndLoop actually gets and sets the value of the Boolean m_EndLoop member variable. Because Kill() will be called on a client thread, you must provide for thread-safe access to m_EndLoop. You can use any of the manual locks: you can lock the whole WorkerThread object using a Monitor, you can use ReaderWriterLock() except that ReaderWriterLock() is excessive for a property that will only be written once. I chose to use a Mutex:
Kill() should return only when the worker thread is dead. To that end, Kill() calls Join(). However, because Kill() is called on the client thread, the WorkerThread object must store as a member variable a Thread object referring to the worker thread. Fortunately, there is already such a member?the m_ThreadObj member variable. You can only store the thread value in the thread method, not in the constructor, which executes on the creating client’s thread. This is exactly what Run() does in this line:
Note that calling Kill() multiple times is harmless. Also note that Kill() does the cleanup of closing the mutex. Finally, what if the client never calls Kill()? To answer that, the WorkerThread class implements IDisposable and a destructor, both calling Kill():
It is important to understand that Kill() is not the same as Dispose(). Kill() is handling execution flow such as application shutdown or timely termination of threads, whereas Dispose() caters to memory and resource management, and disposing of other resources the WorkerThread class might hold. The only reason Dispose() is calling Kill() is as a contingency, in case the client developer forgets to do it.
Share the Post:
![]() ![]() Data Observability Explained
June 8, 2023
Data is the lifeblood of any successful business, as it is the driving force behind critical decision-making, insight generation, and strategic development. However, due to its intricate nature, ensuring the ![]() ![]() Logitech G502 Software: Optimize and Customize Your Gear
June 8, 2023
One of the most significant surges of the 21st century is gaming. Gaming is more popular than ever before thanks to innovative new consoles, high-tech PC setups, mobile gaming improvements, ![]() ![]() Different Types of Data Models Explained with Examples
June 7, 2023
In the modern world, data is everything and everywhere. With so much access to technology, data has become a valuable resource for any business. Albeit a complex one. Data is ![]() ![]() Revolutionizing Search: A Glimpse Into Google’s Generative Experience
June 6, 2023
Google is revolutionizing the search experience as we know it with its latest generative experience. No longer will you be bound by the limitations of traditional keyword searching. Now, you ![]() ![]() 10 Productivity Hacks to Supercharge Your Business in 2023
June 5, 2023
Picture this: your team working seamlessly, completing tasks efficiently, and achieving goals with ease. Sounds like too good to be true? Not at all! With our productivity hacks, you can ![]() ![]() GM Creates Open Source uProtocol and Invites Automakers to Adopt It: Revolutionizing Automotive Software Development.
June 2, 2023
General Motors (GM) recently announced its entry into the Eclipse Foundation. The Eclipse Foundation is a prominent open-source software foundation. In addition, GMC announced its contribution of “uProtocol” to facilitate ![]() ![]() What is Metadata?
June 1, 2023
What is metadata? Well, It’s an odd concept to wrap your head around. Metadata is essentially the secondary layer of data that tracks details about the “regular” data. The regular ![]() ![]() What We Should Expect from Cell Phone Tech in the Near Future
May 31, 2023
The earliest cell phones included boxy designs full of buttons and antennas, and they only made calls. Needless to say, we’ve come a long way from those classic brick phones ![]() ![]() The Best Mechanical Keyboards For Programmers: Where To Find Them
May 29, 2023
When it comes to programming, a good mechanical keyboard can make all the difference. Naturally, you would want one of the best mechanical keyboards for programmers. But with so many ![]() ![]() The Digital Panopticon: Is Big Brother Always Watching Us Online?
May 26, 2023
In the age of digital transformation, the internet has become a ubiquitous part of our lives. From socializing, shopping, and learning to more sensitive activities such as banking and healthcare, ![]() ![]() Embracing Change: How AI Is Revolutionizing the Developer’s Role
May 25, 2023
The world of software development is changing drastically with the introduction of Artificial Intelligence and Machine Learning technologies. In the past, software developers were in charge of the entire development ![]() ![]() The Benefits of Using XDR Solutions
May 24, 2023
Cybercriminals constantly adapt their strategies, developing newer, more powerful, and intelligent ways to attack your network. Since security professionals must innovate as well, more conventional endpoint detection solutions have evolved |