Threading
In a preemptive, multitasking environment, the operating system divides processor time between running process. Threads are the basic unit of execution with in a process or, in the case of .NET, an AppDomain. Each AppDomain starts with a single thread. On single processor machines the appeal of threads is their ability to seemingly execute multiple operations in parallel. Parallel processing becomes an actuality on multiprocessor machines, with threads executing simultaneously on each processor.
Every thread in a process is allocated a prescribed unit o processor time, called a time slice, in which to execute code. Once a thread's time slice, measured in milliseconds, has expired, the runtime saves the thread's context and activates another thread. The duration of the time slice varies between threads, depending on their priority. A thread with highest priority execute for as longer period of time and more frequently, then one set to the lowest priority.
Creating multiple threads with in an AppDomain is both a blessing and a curse. It is a blessing with respect to the ability to order tasks in the background and ensure a timely response to user interface input. Threads allow developers to promote or demote a task's importance by changing a thread's priority. Finally the judicious use of threads avoids hanging an application during long period of blocking.
A Simple Thread Example
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ThreadApplication
{
class Program
{
static void Main(string[] args)
{
ThreadStart startmethod = null;
startmethod = new ThreadStart(ThreadApplication.Program.ThreadStart);
Thread thread = null;
thread = new Thread(startmethod);
thread.Start();
//thread.Join();
Console.WriteLine("Main Exiting");
Console.ReadLine();
}
internal static void ThreadStart()
{
Console.WriteLine("In ThreadStart Method");
}
}
}
The output window will be look like this:
Figure 1: A simple Thread Output.
Let explaining the following code:
The first order of business, after importing the threading class with the using System.Threading; statement, is to create a delegate. The ThreadStart delegate's constructor takes a reference to the method that the running will call when the thread begins execution. ThreadStart is a static method, could simply have been ThreadStart. An instance of the Thread class is created, with a delegate parameter, and its start method is called. The Start method signals the runtime to schedule the newly created thread for execution. How does the runtime accomplish this?
The answer is asynchronously. The call to the starts method returns immediately, most likely before the thread has actually started running. As in the above code there is no way to state categorically which string will be written to the console first. In most cases, it is probable that Main existing will be first. Only if the comments are removed from the Thread.Join call can the order of the string be guaranteed. Recompiling and running the project results will be look like this window:
Figure 2: Simple Thread Output with Thread.Join() Uncommented.
Thread.Join() is an overloaded method that blocks until the calling thread terminates or a timeout occurs.
Thread Synchronization
Thread.join() is a particular type of synchronization. All synchronization classes located in Threading namespace. They are the Monitor, ResetEvent and Mutex classes.
In a multithreaded application, the operating system can preempt one thread for another at any point in time. The preempt can be thought of as "blocking" instigated by the operating system. Unless the developer synchronizes access to variables, bugs will invade an application. Even the most level-headed developers find it frustrating to track down these sorts of bugs. In an ideal world, the runtime would detect such bugs and throw an exception. Unfortunately, what usually happens is that data becomes corrupted and, in the worst case scenario, this is not noticed for months or years.
For examples, suppose you have two threads inserting records into a database. Thread one is creating a first and last name record with the data Napoleon Bonaparte, while thread two's data is Francis Drake. Thread one successfully assigns Napolean to a variable firstName when the operating system preempts thread one in favor of two. Now two is able to set firstName to Francis is before being preempted. Thread one is then assigns Bonaparte to the lastName variables and insert the record into the database. The end result is two records with the names Francis Bonaparte and Napoleon Drake. This mistimed chain of event is called a race condition.
Monitors and the Lock Keyword
Avoiding a race condition by locking objects during access can cause a deadlock. Which results in a hung application. A deadlock is a condition where two threads are each waiting for resource the other has locked. Those familiar with the Win32 application programming interface will recognize that the Monitor class and C# lock keyword are similar to EnterCriticalSection and LeaveCriticalSection, except that the .NET locks define sequential access to objects, where as WIN 32 defines a critical section for a block of code.
The Thread Class
There are various numbers of methods and classes in sealed Thread class. Thread.Sleep() is a static, overloaded method that blocks the current threads for a specific time in milliseconds. The two overloaded method take an int32 or a System.TimeSpan respectively. A zero value indicates that thread is to be suspended for the remainder of its time slice, to permit waiting threads to execute. Timeout.Infinite, in the Threading namespace, block until it is broken out of an infinite sleep by a call to Thread.Interrupt().
IsBackground is a Boolean type property, to identify a thread as being in the foreground or background. The only difference between the two is that a foreground thread's execution is not interrupted as a process terminates. When set to false, a thread prevents the process from terminating until the thread terminates. The default value for this property is false.
A BackgroundThread Example
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ThreadApplication
{
class Program
{
public static void ThreadStart()
{
Console.WriteLine("Thread about to Sleep");
Thread.Sleep(10000);
}
static void Main(string[] args)
{
Thread thread = null;
thread = new Thread(new ThreadStart(ThreadStart));
//thread.IsBackground = true;
thread.Start();
}
}
}
When run this code then the window will look loke this, This window will close after 10 sec because here we set Thread.Sleep(1000):
Figure 3.
Here in this code Main method is exist after the call to ThredStart(), but the process continues to execute until the sleep call in ThreadStart terminates. Removing the comments from the thread.IsBackground statements causes the process to end upon exiting the Main method.
Another property that we can set is the thread priority. The priority property can have one of five ThreadPriority values. The enumeration ranges from lowest to highest with the default value being normal. As threads can have different priorities, they can also be distinguished from one another by use of the Name property. Name is a write one string property that has an empty string as its default value. Once set, any attempt to change the property results in a System.InvalidOperation exception being thrown.
There are also a number of read-only property members. Thread.CurrentThread is a static property that returns a reference to the currently running thread. To establish that a thread has started or running, use the Boolean IsAlive property. A return value of true denotes a running thread, while false signals that a thread has yet to start or has terminated. A more precise enumeration of a thread's state is available through the ThreadState property.
The state diagram as shown in below is an overly simplified illustration of the various states and transition is a thread may go through. Starting from the left, a thread's initial state is always Unstarted and the only possible transition is to a running state. Once a thread has been started, there are a number of possible transitions. In an AbortRequested state, the only possible transformation is to a Stopped state.
Figure 4: Thread Life Cycle.
The ThreadState enumeration has seven possible states. These are the following in below listing.
State |
Method |
Unstarted |
After Thread creation and prior to calling Start() . |
Running |
From Start() to termination or Abort() and when Interrupt() or Resume() is called. |
WaitSleepJoin |
A call to Sleep(), Join(), or one of the Wait object. |
SuspendRequested |
A call to Suspend(). |
AbortRequested |
A call to Abort(). |
Suspended |
The thread is in a suspended state. |
Stopped |
The thread has terminated or responded to an Abort(). |
Listing 1: Thread States.
The Thread.Sleep method applies only to the currently running thread. To selectively pause any thread, the Thread classes provides Suspend() and Resume() to restart a suspended thread.
The following example and the resulting output in figure are fairly self-explanatory; except for the "While" loop in the Main method. If suspended is called prior to the thread being started, a ThreadStateException is thrown. Given that, the application remains in the while loop until the thread has started.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ThreadApplication
{
class Program
{
public static void ThreadStart()
{
Console.WriteLine("Thread Running");
Console.WriteLine("Sleep Entered");
Thread.Sleep(1000);
Console.WriteLine("Sleep Exited");
Console.WriteLine("Thread Exiting");
}
static void Main(string[] args)
{
Thread thread = null;
thread = new Thread(new ThreadStart(ThreadStart));
thread.Start();
while(thread.ThreadState== ThreadState.Unstarted)
{
;
}
thread.Suspend();
Console.WriteLine("Suspend called ");
Thread.Sleep(100);
thread.Resume();
Console.WriteLine("Resume Called");
thread.Join();
Console.Read();
}
}
}
Figure 5: SuspendedThread.
Be aware that a call to Suspend() does not immediately block a thread. Before the runtime can halt a thread's execution, it must reach what is termed a safe point. A safe point is actually a point where a thread can safely be suspended, so that garbage collection can take place without corrupting any references on the thread's stack.
Here I will wrap up the Thread class with a discussion about thread local storage (TLS) and the Abort() method.
TLS is a means to allocate memory for thread-specific data. This memory, called a data slot, may be named or unnamed and is obtained via calls to either Thread.AllocateDataSlot or Thread.AllocateNamedDataSlot. A System.LocalDataStoreSlot reference is returned by both methods. Data is set with a call to SetData() using the data slot reference and an object as parameters. Data is retrieved by calling GetData() with the data slot reference. A particular data slot can not be shared across threads, and a developer must find another mechanism for interthread communication. An alternative to using TLS is to define a System.ThreadStaticAttribute, which is a field that has a separate instance for every executing thread.
Finally, the Abort() terminates a thread by raising a special exception, ThreadAbortException. The exception is special because although it can be caught, it will be rethrown once it leaves a catch block. Thread termination can be halted if the static method Thread.ResetAbort is called with in the catch. In either case, any finally blocks are executed prior to thread termination. As with the suspend method, the runtime marks the thread as requesting termination and the thread continues executing to a safe point. Once the thread has terminated, the state is set to ThreadState.Stopped.
The ThreadPool Class
In many situations, it becomes evident that the constant creation and destruction of threads affects an application's performance. In other circumstances, threads may spend the majority of their lifetime in a sleeping state. In an effort to alleviate these problems, the threading namespace offers a pool of worker threads administered by the runtime. The ThreadPool is a sealed class of static methods, providing up to 25 threads on a pre-processor basis. Only three methods are of interest here named as:
- QueueUserWorkItem ( An Overloaded Method)
- GetAvaliableThread
- GetMaxThreads
The thread pool is also capable of handling asynchronous I/O and timers.
ThreadPooling Example
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ThreadApplication
{
class Program
{
public static void PoolMethod(Object state)
{
Console.WriteLine("In Pool Method");
if (state != null)
{
((AutoResetEvent)state).Set();
}
}
static int Main(string[] args)
{
AutoResetEvent wait = null;
wait = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(PoolMethod), wait);
wait.WaitOne();
//ThreadPool.QueueUserWorkItem(new WaitCallback(PoolMethod));
//Thread.Sleep(1000);
int threads = 0;
int iPorts=0;
ThreadPool.GetAvailableThreads(out threads, out iPorts);
Console.WriteLine("Avaliable Threads :{0} i/o ports: {1}.", threads, iPorts);
ThreadPool.GetMaxThreads(out threads, out iPorts);
Console.WriteLine("Avaliable threads: {0} i/o ports: {1}.",threads, iPorts);
Console.Read();
return(0);
}
}
}
When user run this code then window will look like this:
Figure 6: Thread Pool Output.
Here in the uncommented code the Threadpool.QueueUserWorkItem is called on the main thread with a delegate and an AutoResetEvent reference as parameters. The thread pool calls PoolMethod, which writes a string to the console and sets the event to signaled. Once the main thread is released it writes the thread pool's avaliable and maximum resources to the console and exists. The commented out ThreadPool.QueueUserWorkItem only works if the main threads stays alive or the duration of the PoolMethod execution. The reason for this has to do with the type of thread the threadpool creates, which is a background thread. As explained in the thread class section, a background thread ends with the termination of the last foreground thread. Hence with the main thread sleeping 10 seconds, we are assured that the delegate will have more then enough time to complete its execution before it is destroyed. If the project being created is a Windows Application, the second QueueUserWorkItem can be used. This is because System.Windows.Forms.Application.Run creates an internal message loop and the programme must be explicitly terminated.