Understanding Threading
A thread sometimes called, as lightweight process, is a basic unit of CPU utilization. It consists of a program counter (pointer to the next instruction), a register set and stack space. It shares with its peer thread its code section, data section and operating system resources such as open files and signals collectively known as task. A thread context switch still requires a register set switch but the no memory related work need to be done and it is less expensive compared to switches among the heavy weight processes.
Switching between user level threads can be done independently of the operating system and therefore very quickly. Thus blocking a thread and switching to another thread is a reasonable solution to the problem of how a server can handle many requests efficiently.
Threads can be in one of the several states:
- Ready
- Blocked
- Running
- Terminated
Like processes
When an application executes, a primary thread is created, and the application scope is based on this thread. An Application can create additional threads to perform additional tasks. An example of creating a primary thread is starting Microsoft Word. The Application execution starts the main thread. Within the Word Application, the background printing of a document would be an example of an additional thread being created to handle another task. While you are still interacting with the main thread, the system is carrying out your printing request. After the main application thread is killed, all other threads created as a result of that thread are also killed.
Creating Multithreaded applications
Threading is handled through the System.Threading namespace. The common members of the thread class are listed in the table below.
Common Thread class Members
Member | Description |
CurrentContext | Returns the current Context on which the thread is executing |
CurrentThread | Returns a reference to the currently running Thread |
ResetAbort | Resets an abort request |
Sleep | Suspends a Current Thread for a specified amount of time. |
ApartmentState | Gets or Sets the Apartment state of the Thread |
IsAlive | Gets a value that a indicates whether the thread is been started and is not dead |
IsBackground | Gets a value that a indicates whether the thread is a background thread. |
Name | Gets or Sets the name of the Thread |
Priority | Gets or Sets the thread priority |
ThreadState | Gets the state of the thread |
Abort | Raises the ThreadAbortException,which can end the thread |
Interrupt | Interrupts a thread that is in the waitsleepjoin thread State |
Join | Waits for a thread |
Resume | Resumes a thread that has been suspended |
Start | Begins the thread execution |
Suspend | Suspends the thread |
Creating New Threads
Creating a variable of the System.Threading.Thread type enables you to create a new thread to start working with. Because the concept of threading involves the independent execution of another task, the Thread constructor requires the address of a procedure that will do the work for the thread you are creating. The ThreadStart delegate is the only parameter the constructor needs to begin using the thread.
Code for creating new Threads.
using
System;using
System.Threading;public
class thrds{
publicvoid Threader1()
{
for (int i = 0;i <1000;i++)
{
Console.WriteLine("Thread Name : " + Thread.CurrentThread.Name);
}
}
publicvoid Threader2()
{
for (int i = 0;i <1000;i++)
{
Console.WriteLine("Thread Name : " + Thread.CurrentThread.Name);
}
}
}
public
class ThreadTest{
publicstaticint Main(string<> args)
{
thrds test = new thrds();
Thread t1 = new Thread(new ThreadStart(test.Threader1));
t1.Name = "Threader1";
t1.Start();
Thread t2 = new Thread(new ThreadStart(test.Threader2));
t2.Name = "Threader2";
t2.Start();
Console.ReadLine();
return 0;
}
}
Understanding Thread Priority
For a Thread Procedure to finish before another thread procedure begins you need to set the Priority property to correct ThreadPriority Enumeration to ensure that this thread has priority over any other thread
Ex:
Thread1.Priority = ThreadPriority.Highest
The Thread priority enumeration dictates how a given thread is scheduled based on other running threads. ThreadPriority can be any one of the following
- AboveNormal
- Belownormal
- Highest
- Lowest
- Normal
The Algorithm that determines thread scheduling varies depending on the operating system on which the threads are running. By default when a thread is created it is given a priority of 2 , which is normal in the enumeration.
Thread States
When you create a new thread, you call the start() method. At this point, the operating system allocates time slices to the address of the procedure passed in the thread constructor. Though the thread might live for a very long time, it still passes in different states while other threads are being processed by the operating system. Besides Start, the most common thread states you will use are sleep and abort. By passing number of milliseconds to the sleep constructor, you are instructing the thread to give up the remainder of its time slice. Calling the abort method stops the execution of the thread.
Example code for Sleep and Abort
using
System;using
System.Threading;public
class thrds{
publicvoid Threader1()
{
for (int i = 0;i <1000;i++)
{
if (i == 5 )
{
Thread.Sleep(500);
Console.WriteLine("Thread1 Sleeping");
}
}
}
publicvoid Threader2()
{
for (int i = 0;i <1000;i++)
{
if (i == 5)
{
Thread.Sleep(500);
Console.WriteLine("Thread2 Sleeping");
}
}
}
}
public
class ThreadTest{
publicstaticint Main(string<> args)
{
thrds test = new thrds();
Thread t1 = new Thread(new ThreadStart(test.Threader1));
t1.Start();
Thread t2 = new Thread(new ThreadStart(test.Threader2));
t2.Priority = ThreadPriority.Highest;
t2.Start();
Console.ReadLine();
return 0;
}
}
The Priority property is set to the highest for the second thread. This means that no matter what, it executes before first thread starts. However, in the Threader 2 procedure you have the following if block
for
(int i = 0;i <1000;i++){
if (i == 5)
{
Thread.Sleep(500);
Console.WriteLine("Thread2 Sleeping");
}
}
This tells the t2 thread to sleep for 500 milliseconds, giving up its current time slice and allowing the t1 thread to begin. After both threads are complete, the abort method is called and the threads are killed.
The Thread.Suspend method call, suspends a thread, indefinitely until another thread wakes it back up. To get the thread back on track, you need to call the resume method from another thread so it can restart itself. The following code demonstrates Suspend and Resume method:
Thread.CurrentThread.Suspend;
Console.Writeline("Thread1 Suspended");
Thread.CurrentThread.Resume;
Console.Writeline("Thread1 Resumed");
Thread State is a bitwise combination of the Flags Attribute enumeration. At any given time, a thread can be in more than one state. For example if a thread, a background thread and is currently running, then the state would be both running and background. The table below describes the possible states a thread can be in.
ThreadState Members
Member | Description |
Aborted | The Thread has aborted |
AbortRequested | A request has been made to abort a thread |
Background | The Thread is executing as a background thread |
Running | The thread is being executed |
Suspended | The thread has been suspended |
SuspendRequested | The Thread is being requested to suspend |
Unstarted | The Thread has not been started |
WatSleepJoin | The Thread is blocked on a call to wait sleep or join |
Joining Threads
The Thread.Join Method waits for a thread to finish before continuing processing. This is useful if you create several threads that are supposed to accomplish a certain task, but before you a want the foreground application to continue, you need to ensure that all the threads you created are completed.
The following code demonstrates joining of threads:
using
System;using
System.Threading;public
class thrds{
publicvoid Threader1()
{
for (int i = 0;i <1000;i++)
{
if (i == 5 )
{
Thread.Sleep(500);
Console.WriteLine("Thread1 Sleeping");
}
}
}
publicvoid Threader2()
{
for (int i = 0;i <1000;i++)
{
if (i == 5)
{
Thread.Sleep(500);
Console.WriteLine("Thread2 Sleeping");
}
}
}
}
public
class ThreadTest{
publicstaticint Main(string<> args)
{
thrds test = new thrds();
Thread t1 = new Thread(new ThreadStart(test.Threader1));
t1.Start();
Thread t2 = new Thread(new ThreadStart(test.Threader2));
t2.Priority = ThreadPriority.Highest;
t2.Start();
t1.Join();
Console.WriteLine("Writing");
Console.ReadLine();
return 0;
}
}
Synchronizing threads
When Threads are running, they are sharing time with other running threads. If you have a method that is running on multiple threads, each thread has only several milliseconds of processor time before the operating system preempts the thread to give another thread time in the same method. If you are in the middle of the math statement, or in the middle of concatenating a name, your thread could very well be stopped for several milliseconds and another running thread may overwrite the data. Consider the following code:
{
int y;
int v;
for(int z = 0;z < 20; z++)
{
return y * v;
}
}
It is highly likely that during the loop, a running will stop to allow another thread a chance at this method. When you write multithreaded applications this happens frequently, so you need to know how to address this situation
The following code solves the problem:
Lock(this){
int y;
int v;
for(int z = 0;z < 20; z++)
{
return y * v;
}
}
When a thread reaches lock block, it waits until it can get an exclusive lock on the expression being evaluated before it attempts any further processing. This ensures that multiple threads cannot corrupt shared data.
The Monitor class enables synchronization using the Monitor, Enter, Monitor.TryEnter, and Monitor.Exit methods. After you have a lock on code region, you can use the Monitor.Wait , Monitor.Pulse and Monitor.PulseAll methods to determine if a thread should continue a lock or if any previously locked methods are now available. Wait releases the lock if it is held and waits to be notified. When wait is called, the lock is freed and it returns and obtains the lock again.
Polling and Listening
Polling and Listening are two more instances that represent the usefulness of multithreading. Class libraries, such as System.Net.Sockets include a full range of multithreaded classes that can aid you in creating TCP and UDP Listeners.
The code below uses a timer callback to poll for files in a directory. If a file is found it is promptly deleted. The following sample code expects a C:\Poll Directory. The constructor for the TimeCallback class expects an address for the thread to execute on; an object datatype representing the state of the timer; a due time, which represents a period of time poll until; and a period, which is a millisecond variable indicating when the polling interval occurs.
using
System;using
System.IO;using
System.Threading;namespace
Timer1{
class Class1
{
publicstaticvoid Main()
{
Console.WriteLine("Checking directory updates every 2 Seconds.");
Console.WriteLine("Hit Enter to terminate the program.");
Timer timer = new Timer(new TimerCallback(CheckStatus),null,0,2000);
Console.ReadLine();
timer.Dispose();
}
staticvoid CheckStatus(Object State)
{
string<> str = Directory.GetFiles("C:\\Poll");
if(str.Length > 0)
{
for(int i = 0;i < str.Length;i++)
{
Console.WriteLine(str);
File.Delete(str);
}
}
Console.WriteLine("Directory Empty");
}
}
}
After Running this for a while and periodically copying a few files into the C:\Poll directory, the console output should look similar to that shown in the figure below: