By Manish Panchal
One or more threads run in an AppDomain. An AppDomain is a runtime representation of a logical process within a physical process. A thread is the basic unit to which the operating system allocates processor time. Each AppDomain is started with a single thread, but can create additional threads from any of its threads.
Each thread maintains exception handlers, a scheduling priority, and a set of structures the system will use to save the thread context until it is scheduled. The thread context includes the thread's set of machine registers and stack, in the address space of the thread's process.
Operating systems that support preemptive multitasking create the effect of simultaneous execution of multiple threads from multiple processes. On a multiprocessor computer, some operating systems that are multi-processor aware can simultaneously execute as many threads as there are processors on the computer.
A multitasking operating system divides the available processor time among the processes or threads that need it. The system is designed for preemptive multitasking; it allocates a processor time slice to each thread it executes. The currently executing thread is suspended when its time slice elapses, allowing another thread to run. When the system switches from one thread to another, it saves the thread context of the preempted thread and restores the saved thread context of the next thread in the queue.
The length of the time slice depends on the operating system and the processor. Because each time slice is small, multiple threads appear to be executing at the same time. This is actually the case on multiprocessor systems, where the executable threads are distributed among the available processors. However, caution must be used when using multiple threads in an application, because system performance can decrease if there are too many threads.
When to use threads
A multithreaded process can manage mutually exclusive tasks with threads, such as providing a user interface and performing background calculations. Creating a multithreaded process can also be a convenient way to structure a program that performs several similar or identical tasks concurrently. Your AppDomain could use multiple threads to accomplish the following tasks:
· Communicating over a network, web server and to a database.
· Performing operations that take a large amount of time.
· Distinguish tasks of varying priority. For example, a high-priority thread manages time-critical tasks, and a low-priority thread performs other tasks.
· Allow the user interface to remain responsive, while allocating time to background tasks.
Threads can be expensive and the consumption of OS resources
It is recommended is to use as few threads as possible, thereby minimizing the use of OS resources. This improves performance. Threading has resource requirements and potential conflicts to be considered when designing your application. The resource requirements are as follows:
· The system consumes memory for the context information required by processes, AppDomain and threads. Therefore, the number of processes, AppDomains and threads that can be created is limited by available memory.
· Keeping track of a large number of threads consumes significant processor time. If there are too many threads, most of them will not be able to make significant progress. If most of the current threads are in one process, threads in other processes are scheduled less frequently.
Thread the Unit of execution
The System.Threading.Thread class is an abstraction of the threads that execute within the Runtime. All management of threads is done through the System.Threading.Thread class, which represents a managed thread. This includes threads created by the Runtime and those created outside the Runtime that happen to be "wandering" into the Runtime environment to execute some managed code.
Win32 .NET Runtime
CreateThread Combination of Thread and ThreadStart. For example if the programmer was using CreateThread(…,&Method,…) they would now use new Thread(new ThreadStart(&oFoo::Method)).
TerminateThread Thread.Abort
SuspendThread Thread.Suspend
ResumeThread Thread.Resume
Sleep Thread.Sleep
WaitForSingleObject on the thread handle Thread.Join
ExitThread
GetCurrentThread Thread.CurrentThread
SetThreadPriority Thread.Priority
No equivalent Thread.Name
No equivalent Thread.IsBackground
No equivalent Thread.ApartmentState
Creating Threads
When an AppDomain is created, the Runtime creates a thread to execute the code within that AppDomain. If the code being executed is managed code, then a System.Threading.Thread object for that thread can be obtained by retrieving the static property on the thread class Thread.CurrentThread.
Creating a new instance of a System.Threading.Thread object can create new managed threads. The constructor for System.Threading.Thread takes, as its only parameter, a Thread Delegate.
A delegate is an object that acts as reference to a method on another class. A thread delegate is a special type of delegate that references a method that will be executed by a new thread.
ThreadStart
Once you have an instance of a delegate, you can pass the delegate to the constructor of a ThreadStart object. The ThreadStart object is passed to the Thread constructor. The Thread function does not begin executing until Thread.Start is called. Once Thread.Start is called, execution begins at the first line of the method referred to by the thread delegate. Calling Thread.Start more than once will cause a ThreadStateException to be thrown.
Thread.Start – Submits a start request, this is an asynchronous / request call. The method may return even while the thread has not actually started. Use Thread.ThreadState and Thread.IsAlive to determine the state of the thread.
Thread.Abort – Aborts a thread. Results in the thread's death. Use Thread.ThreadState and Thread.IsAlive to determine the state of the thread.
Working With Threads
Once a thread has been started, it's often useful for that thread to pause for a fixed period of time. Calling Thread.Sleep causes the thread to immediately block for a fixed number of milliseconds. The Sleep method takes as a parameter a timeout, which is the number of milliseconds that the thread should remain blocked. The Sleep method is called when a thread wants to put itself to sleep. One thread cannot call Sleep on another thread. Calling Thread.Sleep(0) causes a thread to yield the remainder of its timeslice to another thread. Calling Thread.Sleep(Timeout.Infinite) causes a thread to sleep until it is interrupted by another thread that calls Thread.Interrupt.
A thread can also be paused by calling Thread.Suspend. When a thread calls Thread.Suspend on itself, the call blocks until the thread is resumed by another thread. When one thread calls Thread.Suspend on another thread, the call is a non-blocking call that causes the other thread to pause. Calling Thread.Resume breaks another thread out of the suspend state and causes the thread to resume execution. Calling Thread.Resume causes the thread to resume execution regardless of how many times Thread.Suspend was called. For example, if Thread.Suspend is called 5 consecutive times, then Thread.Resume is called, the thread will resume execution immediate following the call to Resume (it does not take 5 calls to Thread.Resume).
Unlike Thread.Sleep, Thread.Suspend does not cause a thread to immediately stop execution. The Runtime must wait until the thread has reached a safe point (defined below) before it can suspend the thread. A thread cannot be suspended if it has not been started or if it has stopped.
Threads can be blocked for any of several reasons. For example, a thread may be waiting for another thread to die i.e. Thread.Join, it may be waiting for access to a synchronized object i.e. Monitor.Wait, or it may just be sleeping i.e. Thread.Sleep. A thread that is waiting can be interrupted by another thread by calling Thread.Interrupt on the blocked thread. When a thread is interrupted, a ThreadInterruptedException is thrown which causes the thread to break out of the blocking call. The thread should catch the ThreadInterruptedException and do whatever is appropriate to continue working. If the thread ignores the exception, the Runtime will catch the exception and kill the thread but not before displaying a message informing the user that the thread was killed prematurely. Thread.Interrupt will also interrupt a thread that is waiting for access to a synchronized region of code.
· Thread.Interrupt wakes the destination thread out of any wait it may be in and causes a particular exception to be thrown in the destination thread.
· Thread.Abort is similar to Thread.Interrupt, but it does not wake the destination thread out of a wait. Instead, when the thread returns normally from the wait it will throw the ThreadAbortException is a special one that cannot be caught from managed code (although finally blocks are executed).
Safe point
When a Thread.Suspend is performed on a thread. The CLR does not perform the action immediately. The Runtime records that a thread suspend has been requested and waits until the thread has reached a safepoint before suspending the thread. A safepoint for a thread is a safe point for GC. If someone provokes a GC and the Runtime can successfully hijack a thread for the GC, the Runtime will Suspend it at that time. Hijacking is the act of bashing a return address on the stack so it returns to Runtime, rather than to its caller. This allows the Runtime to take control of threads at return points.
If a loop contains a control path with no calls somehow yield for a GC (if it had calls, hijacking would snag the thread for GC), the regular JIT detects this situation and makes the entire method fully interruptible. All instructions in the method are GC safe, because it creates GC tables for every instruction.
Destroying Threads
The Thread.Abort method is used to Abort a thread permanently. When Abort is called, the Runtime throws ThreadAbortException. The thread cannot catch the exception, but the Runtime executes finally blocks, if they are present. The CLR quietly kills the thread without informing the user.
As Thread.Abort does not cause the thread to abort immediately (it waits for the thread to reach a safe point), the caller must wait on the thread if it needs to be sure the thread is indeed stopped. The caller can do this by calling Thread.Join. Join is a blocking call that does not return until the thread has actually stopped executing. Once a thread is aborted, it cannot be restarted. Calls to Thread.Suspend or Thread.Resume on a stopped thread are ignored.
Once threads have been aborted they cannot be restarted. For example if code creates a thread, lets it run, then abors the thread. If it tries to restart the thread a ThreadStateException is thrown.
You can also call Thread.Join(int timeout) and specify a timeout period. If the thread dies before the timeout has elapsed, the call returns True. Otherwise, if the time expires before the thread dies, the call returns False. Threads that are waiting on a call to Thread.Join can be interrupted by other threads that call Thread.Interrupt.
Scheduling Threads
Every thread has a thread priority assigned to it. Threads created within the Runtime are initially assigned the priority of Normal. Threads created outside the Runtime retain the priority they had before they entered the Runtime. You can get the priority of any thread with the Thread.Priority property and you can change the priority of any thread with Thread.Priority property.
Threads are scheduled for execution based on their priority. Even though the threads are executing within the Runtime, the threads are assigned CPU time by the OS. The details of the scheduling algorithm used to determine the order in which threads are executed varies with each OS. Under some operating systems, the thread with the highest priority (of those threads that are runable can be executed) is always scheduled to run first. If multiple threads with the same priority are all available, the scheduler does a round robin among the threads at that priority giving each thread a fixed timeslice in which to execute. As long as a thread with a higher priority is available to run, lower priority threads do not get to execute. Once there are no more runable threads at a given priority that can be executed, the scheduler moves to the next lower priority and schedules the threads at that priority for execution. Once a higher priority thread becomes runable to be executed, the lower priority thread is preempted and the higher priority thread is allowed to execute once again. On top of all that, the operating system can also adjust thread priorities dynamically as an application's UI is moved between foreground and background. Other operating systems may choose to use a different scheduling algorithm.
Thread States
A thread can be in multiple states at the same time. The property Thread.ThreadState provides a bit mask indicating the thread's current state. A thread is always in at least one of these possible states in the ThreadState enum.
Threads created within the Runtime are initially in the Unstarted state. The thread remains in the Unstarted state until it is transitioned into the started state by calling Thread.Start. External threads that wander in to the Runtime are already in the started state. Once in the started state, there are a number of actions that can cause the thread to change states. The following table lists the actions that cause a change of state along with the corresponding new state.
Action State Transition
Another thread calls Thread.Start Unchanged
The thread starts running Running
The thread calls Thread.Sleep WaitSleepJoin
The thread calls Monitor.Wait on another object WaitSleepJoin
The thread calls Thread.Join on another thread WaitSleepJoin
Another thread calls Thread.Suspend SuspendRequested
The thread responds to a Thread.Suspend request Suspended
Another thread calls Thread.Resume Running
Another thread calls Thread.Interrupt Running
Another thread calls Thread.Abort AbortRequested
The thread response to a Thread.Abort Aborted
It is often the case that a thread is in more than one state at any given time. For example, if a thread is blocked on a call to Wait and another thread calls Abort on that same thread, the thread will be in both the Waiting and the AbortRequested state at the same time. In that case, as soon as the thread returns from the call to Wait or is interrupted, it will receive the ThreadAbortException.
Once a thread leaves the Unstarted state as the result of a call to Thread.Start, it can never return to the Unstarted state. A thread can never leave the Stopped state, either.
The following diagram illustrates the thread states and the method calls that would cause the thread to leave each state.
Foreground and Background Threads
In the Runtime, a thread is either a background thread or a foreground thread. Background threads are identical to foreground threads with one exception. Unlike a foreground thread, a background thread will not keep the Runtime alive. Once all foreground threads have been stopped, the Runtime kills all background threads and shuts down. A thread can be designated as a background or a foreground thread by setting the Thread.IsBackground property. For example a foreground thread can be designated a background thread by calling Thread.IsBackground = True. A background thread can be designated a foreground thread by calling Thread.IsBackground = False.
The runtime kills the background threads by throwing a ThreadAbortException.
Thread Local Store Key Points
· Data slots per thread.
The Thread Local Store (TLS) provides data slots for the thread. The data slots are unique per thread i.e. the state is not shared across threads. Slot types:
· Allocated Data Store Slot. A slot is allocated and a slot object is returned. The slot object is used to access the data slot. The slot is freed automatically.
· Named Slots. A name is provide and a slot is allocated and a slot object is returned. The slot object is used to access the data slot. The slot can be freed manually.
Apartments
A thread can be marked to indicate that it will host a single-threaded or multi-threaded apartment. The Thread.ApartmentState property returns and assigns the apartment state of a thread. If the property has not been set then the property returns Unknown. The property can be set when the thread is in the Unstarted or Running state, however it can be only be set once for a thread. The two valid property states are STA or MTA.
Threads and Garbage Collection
In order to perform a GC, all the threads must be suspended - except for the thread performing the GC, of course. Each thread must be brought to a "safe place" before it can be suspended. It has already been noted that threads might have to be brought to a safe place for other reasons like aborting a thread or the programmatic Thread.Suspend service. The Runtime supports bringing threads to a safe spot for the purposes of GC.
Thread Store
The Runtime tracks all the interesting threads in its process. Threads are interesting if they have ever executed code within the Runtime. In other words, the Runtime only tracks the threads it knows about.
Threads can enter the Runtime through COM Interop, because we expose the runtime objects out as COM objects. Other COM (not runtime) threads can then make calls on those exposed objects. Other ways a thread can enter the Runtime include DllGetClassObject() and PInvoke.
When one of our gateways, like a COM-Callable-Wrapper, is invoked on a thread, the gateway checks the TLS of that thread. It's looking for one of our internal Thread objects. If one is found, we already know about this thread. Otherwise, it's a thread we've never seen before. So we construct a new Thread object and install it in the TLS of that thread.
In addition to placing all our state about the thread in the Thread object in its TLS, we also keep a list of all the threads we've seen. This is the ThreadStore. It is used during thread suspension for GC and during product shutdown.
Note:------------------------------
Article views and concepts are collected from Microsoft Developer Network and other Microsoft technical references.
-----------------------------------