Threading Overview In C#

Definition

We can define Thread as a small set of executable instructions and this set of instructions can be used to isolate a task from a process. To obtain parallelism and give interactive user interaction to the application one of the most efficient way is to implement multiple threads. Tasks and Threads are related with each other. A Task is something that we want to do and a Thread is one of the possible workers that perform the Task. Threads are often known as lightweight processes. Dot Net framework has thread-associate classes in System.Threading namespace.

Creating a Thread

We have to create a call back function first which will be a starting point for our new Thread. The block of code below illustrates the example of a simple Thread.

  1. class Program  
  2. {  
  3.     //Call back function for Thread  
  4.     public static void MyCallBackFunction()  
  5.     {  
  6.         int i = 0;  
  7.         while (i < 4)   
  8.         {  
  9.             Console.WriteLine("Hello new Thread..");  
  10.             i++;  
  11.         }  
  12.     }  
  13.     static void Main(string[] args)   
  14.     {  
  15.         //Create an object for the Thread  
  16.         Thread myThread = new Thread(new ThreadStart(MyCallBackFunction));  
  17.         //To start a Thread  
  18.         myThread.Start();  
  19.         //To abort a Thraed throwing the ThreadAbortException  
  20.         //myThread.Abort();  
  21.         //To suspant a Thread  
  22.         //myThread.Suspend();  
  23.         //To resume a Thread  
  24.         //myThread.Resume();  
  25.         Console.ReadKey();  
  26.     }  
Example Output

Hello new Thread..
Hello new Thread..
Hello new Thread..
Hello new Thread..


Interaction between Threads

After starting a Thread we don't have to stop or free the Thread. This is done automatically by the .NET framework common language runtime. The example below demonstrate how to interact between two threads running simultaneously within the same process. The program execution begins by creating an object of class ThreadDemo that reference the Demo() method. The IsAlive property allows the program to wait until the thread is initialized and the Sleep() method tells the thread to give up its time slice and also stop executing for a certain user define period measured in milliseconds. After that the Thread is stopped and joined. The functionality of joining a thread is to make the main thread wait for it to die or for a specified time to expire.
  1. public class ThreadDemo   
  2. {  
  3.     public void Demo()  
  4.     {  
  5.         while (true)   
  6.         {  
  7.             Console.WriteLine("ThreadDemo.Demo is running under own thread..");  
  8.         }  
  9.     }  
  10. };  
  11.   
  12. class Program   
  13. {  
  14.     public static int Main()   
  15.     {  
  16.         Console.WriteLine("Example of Thread Start, Stop and Join..");  
  17.         ThreadDemo oAlpha = new ThreadDemo();  
  18.         // Create the thread object and passing this object in ThreadDemo.Demo method  
  19.         // throughout a ThreadStart delegate. It does not start the thread.  
  20.         Thread oThread = new Thread(new ThreadStart(oAlpha.Demo));  
  21.         oThread.Start();  
  22.         // Spin for a moment waiting for the started thread to become  
  23.         // The Thread will be Alive:  
  24.         while (!oThread.IsAlive);  
  25.         // Main thread is keeping to sleep for 1 millisecond to allow oThread  
  26.         // in order to executre some work  
  27.         Thread.Sleep(1);  
  28.         oThread.Abort();  
  29.         // In this point Wait until oThread is finished. Join also has overloads  
  30.         // that take a millisecond interval.  
  31.         oThread.Join();  
  32.         Console.WriteLine();  
  33.         Console.WriteLine("ThreadDemo.Demo has finished..");  
  34.         try   
  35.         {  
  36.             Console.WriteLine("Trying to restart ThreadDemo.Demo..");  
  37.             oThread.Start();  
  38.         } catch (ThreadStateException)   
  39.         {  
  40.             Console.Write("ThreadStateException restarting ThreadDemo.Demo..");  
  41.             Console.WriteLine("Expected aborted! threads cannot be restarted..");  
  42.         }  
  43.         Console.ReadKey();  
  44.         return 0;  
  45.     }  
  46. }
Example Output

Thread Start/Stop/Join Sample
ThreadDemo.Demo is running under own thread..
ThreadDemo.Demo is running under own thread..
ThreadDemo.Demo is running under own thread..
...
...
ThreadDemo.Demo has finished..
Trying to restart ThreadDemo.Demo thread..
ThreadStateException restarting ThreadDemo.Demo..
Expected aborted! threads cannot be restarted..


Nonblocking Synchronization- Full Fence

Full memory barrier (full fence) is the simplest kind of memory barrier. It prevents instruction reordering or caching around that fence calling Thread.MemoryBarrier.

Application of full fences-
  1. class Question   
  2. {  
  3.     int answer;  
  4.     bool complete;  
  5.     void APart()   
  6.     {  
  7.         answer = 10;  
  8.         // Thread Barrier 1  
  9.         Thread.MemoryBarrier();  
  10.         complete = true;  
  11.         // Thread Barrier 2  
  12.         Thread.MemoryBarrier();  
  13.     }  
  14.     void BPart()   
  15.     {  
  16.         // Thread Barrier 3  
  17.         Thread.MemoryBarrier();  
  18.         if (complete)   
  19.         {  
  20.             // Thread Barrier 4  
  21.             Thread.MemoryBarrier();  
  22.             Console.WriteLine(answer);  
  23.         }  
  24.     }  
Here, the Thread Barriers 1 and 4 prevent the above code example from writing 0 and the Thread Barriers 2 and 3 ensure that if B ran after A, reading _complete would evaluate to true.

Every individual read or write does not require a full fence and for the three answer field also, the example above would need four fences.
  1. class Question   
  2. {  
  3.     int answer1, answer2, answer3;  
  4.     bool complete;  
  5.     void APart()   
  6.     {  
  7.         answer1 = 10;  
  8.         answer2 = 15;  
  9.         answer3 = 20;  
  10.         // Thread Barrier 1  
  11.         Thread.MemoryBarrier();  
  12.         complete = true;  
  13.         // Thread Barrier 2  
  14.         Thread.MemoryBarrier();  
  15.     }  
  16.     void BPart()   
  17.     {  
  18.         // Thread Barrier 3  
  19.         Thread.MemoryBarrier();  
  20.         if (complete)   
  21.         {  
  22.             // Thread Barrier 4  
  23.             Thread.MemoryBarrier();  
  24.             Console.WriteLine(answer1 + answer2 + answer3);  
  25.         }  
  26.     }  
  27. }
The Volatile Keyword

Another advanced way to solve this problem is to use the volatile keyword to the _complete field.

volatile bool _complete;

The compiler gets instruction from the volatile keyword in order to generate an acquire fence on every read and a release fence on every write. Application of volatile does not prevent a write followed by a read from being swapped.

For example, if Method1 and Method2 simultaneously run on different threads then for a and b both end up with a value of 0.
  1. class Volatility   
  2. {  
  3.     volatile int p, q;  
  4.     // Executed on one thread  
  5.     void Method1()   
  6.     {  
  7.         // Volatile write (release-fence)  
  8.         p = 1;  
  9.         // Volatile read (acquire-fence)   
  10.         int a = q;...  
  11.     }  
  12.     // Executed on another thread  
  13.     void Method2()  
  14.     {  
  15.         // Volatile  
  16.         write (release-fence)  
  17.         q = 1;  
  18.         // Volatile read (acquire-fence)   
  19.         int b = p;...  
  20.     }  
  21. }
VolatileRead and Volatile write

The static VolatileRead() and VolatileWrite() methods in Thread read or write a variable at the time of enforcing generated by the volatile keyword. The implementations of these two methods are relatively inefficient though the way they create full fences.

VolatileWrite method
  1. public static void VolatileWrite(ref int addr, int val)   
  2. {  
  3.     MemoryBarrier();  
  4.     addr = val;  
  5. }
VolatileRead method
  1. public static int VolatileRead(ref int addr)  
  2. {  
  3.    int n = addr; MemoryBarrier(); return n;  
  4. }


Similar Articles