Multithreading With .NET

Multithreading allows a program to run multiple threads concurrently. This article explains how multithreading works in .NET. This article covers the entire range of threading areas from thread creation, race conditions, deadlocks, monitors, mutexes, synchronization and semaphores and so on.

Introduction to multithreading

 
This article explains how multithreading works. You will learn how the operating system manages thread execution and shows you how to manipulate the Thread class in your program to create and start managed threads.
 
This article covers the entire range of threading areas from thread creation, race conditions, deadlocks, monitors, mutexes, synchronization and semaphores and so on.
 

Multithreading Overview

 
A thread is an independent stream of instructions in a program. A thread is similar to a sequential program. However, a thread itself is not a program, it can't run on its own, instead it runs within a program's context.
 
The real usage of a thread is not about a single sequential thread, but rather using multiple threads in a single program. Multiple threads running at the same time and performing various tasks is referred as Multithreading. A thread is considered to be a lightweight process because it runs within the context of a program and takes advantage of resources allocated for that program.
Multithreading with .NET 
With the task manager, you can turn on the Thread column and see the processes and the number of threads for every process. Here, you can notice that only cmd.exe is has a single thread whereas all other applications use multiple threads.
 
Multithreading with .NET 
 
The operating system schedules threads. A thread has a priority and every thread has its own stack, but the memory for the program code and heap are shared among all threads of a single process.
 
A process consists of one or more threads of execution. A process always consists of at least one thread called the primary thread (Main() method in C# programs). A single-threaded process contains only one thread while a multithreaded process contains more than one thread for execution.
Multithreading with .NET 
On a computer, the operating system loads and starts applications. Each application or service runs as a separate process on the machine. The following image illustrates that there are quite a few processes actually running than there are actual applications running in the system. Many of these processes are background operating system processes that are started automatically when the OS loads.
 
Multithreading with .NET 
 
System.Threading Namespace
 
Like many other features, in .NET, System.Threading is the namespace that provides various types to help in construction of multithreaded applications.
 
Type Description
Thread It represents a thread that executes within the CLR. Using this, we can produce additional threads in an application domain.
Mutex It is used for synchronization between application domains.
Monitor It implements synchronization of objects using Locks and Wait.
Smaphore It allows limiting the number of threads that can access a resource concurrently.
Interlock It provides atomic operations for variables that are shared by multiple threads.
ThreadPool It allows you to interact with the CLR maintained thread pool.
ThreadPriority This represents the priority level such as High, Normal, Low.
 
System.Threading.Thread class
 
The Thread class allows you to create and manage the execution of managed threads in your program. These threads are called managed threads.
 
Member Type Description
CurrentThread Static Return a reference of current running thread.
Sleep Static Suspend the current thread for a specific duration.
GetDoamin Static Return a reference of current application domain.
CurrentContext Static Return a reference of current context in which the thread currently running.
Priority Instance level Get or Set the Thread priority level.
IsAlive Instance level Get the thread state in form of True or False value.
Start Instance level Instruct the CLR to start the thread.
Suspend Instance level Suspend the thread.
Resume Instance level Resume a previously suspended thread.
Abort Instance level Instruct the CLR to terminate the thread.
Name Instance level Allows establishing a name to thread.
IsBackground Instance level Indicate whether a thread is running in background or not.
 

Multithreading Implementation

 
Obtaining Current Thread Information
 
To illustrate the basic use of the Thread type, suppose you have a console application in which the CurrentThread property retrieves a Thread object that represents the currently executing thread.
  1. using System;  
  2. using System.Threading;  
  3. namespace threading  
  4. {  
  5.     class Program  
  6.     {  
  7.         static void Main(string[] args)  
  8.         {  
  9.             Console.WriteLine("**********Current Thread Informations***************\n");  
  10.             Thread t = Thread.CurrentThread;  
  11.             t.Name = "Primary_Thread";  
  12.             Console.WriteLine("Thread Name: {0}", t.Name);  
  13.             Console.WriteLine("Thread Status: {0}", t.IsAlive);  
  14.             Console.WriteLine("Priority: {0}", t.Priority);  
  15.             Console.WriteLine("Context ID: {0}", Thread.CurrentContext.ContextID);  
  16.             Console.WriteLine("Current application domain: {0}",Thread.GetDomain().FriendlyName);  
  17.             Console.ReadKey();   
  18.         }  
  19.     }  
  20. }  
After compiling this application, the output will be as in the following;
 
Multithreading with .NET 
 
Simple Thread Creation
 
The following example explains the Thread class implementation in which the constructor of Thread class accepts a delegate parameter. After the Thread class object is created, you can start the thread with the Start() method as in the following;
  1. using System;  
  2. using System.Threading;  
  3. namespace threading  
  4. {  
  5.     class Program  
  6.     {  
  7.         static void Main(string[] args)  
  8.         {  
  9.             Thread t = new Thread(myFun);  
  10.              t.Start();  
  11.             Console.WriteLine("Main thread Running");  
  12.             Console.ReadKey();   
  13.         }  
  14.         static void myFun()  
  15.         {  
  16.             Console.WriteLine("Running other Thread");   
  17.         }  
  18.     }  
  19. }  
After running the application, you will get the following output of the two threads:
 
Multithreading with .NET 
 
The important point to be noted here is that, there is no guarantee of what output comes first, in other words which thread starts first. Threads are scheduled by the operating system. So which thread comes first can be different each time.
 
Background Thread
 
The process of the application keeps running as long as at least one foreground thread is running. If more than one foreground thread is running and the Main() method ends, the process of the application keeps active until all foreground threads finish their work, and by foreground thread termination all background threads will terminate immediately.
 
When you create a thread with the Thread class, you can define it as being either a foreground or background thread by setting the property IsBackground. The Main() method sets this property of the thread "t" to false. After setting the new thread, the main thread just writes to the console an end message. The new thread writes a start and an end message, and in between it sleeps for 2 seconds.
  1. using System;  
  2. using System.Threading;  
  3. namespace threading  
  4. {  
  5.     class Program  
  6.     {  
  7.         static void Main(string[] args)  
  8.         {  
  9.             Thread t = new Thread(myFun);  
  10.             t.Name = "Thread1";  
  11.             t.IsBackground = false;   
  12.             t.Start();  
  13.             Console.WriteLine("Main thread Running");  
  14.             Console.ReadKey();   
  15.         }  
  16.         static void myFun()  
  17.         {  
  18.             Console.WriteLine("Thread {0} started", Thread.CurrentThread.Name);  
  19.             Thread.Sleep(2000);   
  20.             Console.WriteLine("Thread {0} completed", Thread.CurrentThread.Name);   
  21.         }  
  22.     }  
  23. }  
When you compile this application, you will still see the completion message written to the console because the new thread is a foreground thread. Here, the output is as in the following;
 
Multithreading with .NET 
 
If you change the IsBackground property to true then the result shown at the console will be as in the following;
 
Multithreading with .NET 
 
Concurrency issues 
 
When starting multiple threads that access the same data, your program needs to ensure that any piece of shared data is protected against the possibility of numerous threads changing its value.
 
Race Condition
 
A race condition occurs if two or more threads access the same object and access to the shared state is not synchronized. To illustrate the problem of a Race condition, let's build a console application. This application uses the Test class to print 10 numbers by pausing the current thread for a random number of times. 
  1. using System;  
  2. using System.Threading;  
  3. namespace threading  
  4. {  
  5.     public class Test  
  6.     {  
  7.         public void Calculate()  
  8.         {    
  9.             for (int i = 0; i < 10; i++)  
  10.             {  
  11.                 Thread.Sleep(new Random().Next(5));  
  12.                 Console.Write(" {0},", i);  
  13.             }  
  14.             Console.WriteLine();  
  15.         }  
  16.     }  
  17.     class Program  
  18.     {  
  19.         static void Main(string[] args)  
  20.         {  
  21.             Test t = new Test();  
  22.             Thread[] tr = new Thread[5];  
  23.             for (int i = 0; i < 5; i++)  
  24.             {  
  25.                 tr[i] = new Thread(new ThreadStart(t.Calculate));  
  26.                 tr[i].Name = String.Format("Working Thread: {0}", i);  
  27.             }  
  28.             //Start each thread  
  29.             foreach (Thread x in tr)  
  30.             {  
  31.                 x.Start();  
  32.             }  
  33.             Console.ReadKey();  
  34.         }  
  35.     }  
  36. }  
After compiling this program, the primary thread within this application domain begins by producing five secondary threads. And each working thread is told to call the Calculate() method on the same Test class instance. Hence, all five threads start to access the Calculate() method simultaneously and since we have not taken any precaution to lock down this object's shared resources; this will lead towards the Race Condition and the application produces unpredictable output as in the following;
 
Multithreading with .NET 
 
Deadlocks
 
Having too much locking in an application can get your application into trouble. In a deadlock, at least two threads wait for each other to release a lock. As both threads wait for each other, a deadlock situation occurs and threads wait endlessly and the program stops responding.
 
Here, both the methods changed the state of objects obj1 and obj2 by locking them. The method DeadLock1() first locks obj1 and next for obj2 similarly method DeadLock2() first locks obj2 and then obj1. So lock for obj1 is released, next a thread switch occurs and the second method starts and gets the lock for obj2. The second thread now waits for the lock of obj1. Both of threads now wait and don't release each other. This is typically a deadlock situation.
  1. using System;  
  2. using System.Threading;  
  3. namespace threading  
  4. {  
  5.     class Program  
  6.     {  
  7.         static object obj1 = new object();  
  8.         static object obj2 = new object();  
  9.         public static void DeadLock1()  
  10.         {  
  11.             lock (obj1)  
  12.             {  
  13.                 Console.WriteLine("Thread 1 got locked");  
  14.                 Thread.Sleep(500);  
  15.                 lock (obj2)  
  16.                 {  
  17.                     Console.WriteLine("Thread 2 got locked");  
  18.                 }  
  19.             }  
  20.         }  
  21.         public static void DeadLock2()  
  22.         {  
  23.             lock (obj2)  
  24.             {  
  25.                 Console.WriteLine("Thread 2 got locked");  
  26.                 Thread.Sleep(500);  
  27.                 lock (obj1)  
  28.                 {  
  29.                     Console.WriteLine("Thread 1 got locked");  
  30.                 }  
  31.             }  
  32.         }  
  33.         static void Main(string[] args)  
  34.         {  
  35.             Thread t1 = new Thread(new ThreadStart(DeadLock1));  
  36.             Thread t2 = new Thread(new ThreadStart(DeadLock2));  
  37.             t1.Start();  
  38.             t2.Start();  
  39.             Console.ReadKey();  
  40.         }  
  41.     }  
  42. }  
Synchronization
 
Problems that can occur with multiple threads such as Race condition and deadlocks can be avoided by Synchronization. It is always suggested to avoid concurrency issues by not sharing data between threads. Of course, this is not always possible. If data sharing is unavoidable then you must use synchronization so that only one thread at a time accesses and changes shared states.
 
This section discusses various synchronization techniques.
 
Locks
 
We can synchronize access to shared resources using the lock keyword. By doing so, incoming threads cannot interrupt the current thread, preventing it from finishing its work. The lock keyword requires an object reference.
 
By taking the previous Race Condition problem, we can refine this program by implementing a lock on crucial statements to make it foolproof from a race conditions as in the following;
  1. public class Test  
  2. {  
  3.     public object tLock = new object();  
  4.     public void Calculate()  
  5.     {  
  6.         lock (tLock)  
  7.         {  
  8.             Console.Write(" {0} is Executing",Thread.CurrentThread.Name);  
  9.             for (int i = 0; i < 10; i++)  
  10.             {  
  11.                 Thread.Sleep(new Random().Next(5));  
  12.                 Console.Write(" {0},", i);  
  13.             }  
  14.             Console.WriteLine();  
  15.         }  
  16.     }  
  17. }  
After compiling this program, this time it produced the desired result as in the following. Here, each thread has sufficed opportunity to finish its tasks.
 
Multithreading with .NET 
 
Monitor
 
The lock statement is resolved by the compiler to the use of the Monitor class. The Monitor class is almost similar to a lock but its advantage is better control than the lock statement. You are able to instruct the lock's enter and exit explicitly, as shown in the code below. 
  1. object tLock = new object();  
  2. public void Calculate()  
  3. {  
  4.     Monitor.Enter(tLock);  
  5.     try  
  6.     {  
  7.       for (int i = 0; i < 10; i++)  
  8.       {  
  9.         Thread.Sleep(new Random().Next(5));  
  10.         Console.Write(" {0},", i);  
  11.       }  
  12.     }  
  13.     catch{}  
  14.     finally  
  15.     {  
  16.       Monitor.Exit(tLock);  
  17.     }  
  18.     Console.WriteLine();  
  19. }  
In fact, if you observe the IL code of the any application that uses the lock statement, you will find the Monitor class reference there as in the following;
 
Multithreading with .NET 
 
Using [Synchronization] Attribute
 
The [Synchronization] attribute is a member of System.Runtime.Remoting.Context namespace. This class level attribute effectively locks down all instance of the object for thread safety.
  1. using System.Threading;  
  2.   
  3. using System.Runtime.Remoting.Contexts;   
  4. [Synchronization]  
  5. public class Test:ContextBoundObject  
  6. {  
  7.     public void Calculate()  
  8.     {  
  9.         for (int i = 0; i < 10; i++)  
  10.         {  
  11.             Thread.Sleep(new Random().Next(5));  
  12.             Console.Write(" {0},", i);  
  13.         }  
  14.         Console.WriteLine();  
  15.     }  
  16. }  
Mutex
 
Mutex stands for Mutual Exclusion that offers synchronization across multiple threads. The Mutex calss is derived from WaitHandle, you can do a WaitOne() to acquire the mutex lock and be the owner of the mutex that time. The mutex is released by invoking the ReleaseMutex() method as in the following;
  1. using System;  
  2. using System.Threading;  
  3. namespace threading  
  4. {  
  5.     class Program  
  6.     {  
  7.         private static Mutex mutex = new Mutex();  
  8.         static void Main(string[] args)  
  9.         {  
  10.             for (int i = 0; i < 4; i++)  
  11.             {  
  12.                 Thread t = new Thread(new ThreadStart(MutexDemo));  
  13.                 t.Name = string.Format("Thread {0} :", i+1);  
  14.                 t.Start();  
  15.             }  
  16.             Console.ReadKey();  
  17.         }  
  18.         static void MutexDemo()  
  19.         {  
  20.             try  
  21.             {  
  22.                 mutex.WaitOne();   // Wait until it is safe to enter.  
  23.                 Console.WriteLine("{0} has entered in the Domain", Thread.CurrentThread.Name);  
  24.                 Thread.Sleep(1000);    // Wait until it is safe to enter.  
  25.                 Console.WriteLine("{0} is leaving the Domain\r\n", Thread.CurrentThread.Name);  
  26.             }  
  27.             finally  
  28.             {  
  29.                 mutex.ReleaseMutex();  
  30.             }  
  31.         }  
  32.     }  
  33. }  
Once you have successfully compiled this program, it shows when each new thread first enters into its application domain. Once it has finished its tasks then it is released and the second thread starts and so on.
 
Multithreading with .NET 
 
Semaphore
 
A semaphore is very similar to a Mutex but a semaphore can be used by multiple threads at once while a Mutex can't. With a Semaphore, you can define a count of how many threads are allowed to access the resources shielded by a semaphore simultaneously.
 
Here in the following example, 5 threads are created and 2 semaphores. In the constructor of the semaphore class, you can define the number of locks that can be acquired with a semaphore. 
  1. using System;  
  2. using System.Threading;  
  3. namespace threading  
  4. {  
  5.     class Program  
  6.     {  
  7.         static Semaphore obj = new Semaphore(2, 4);  
  8.         static void Main(string[] args)  
  9.         {  
  10.             for (int i = 1; i <= 5; i++)  
  11.             {  
  12.                 new Thread(SempStart).Start(i);  
  13.             }  
  14.             Console.ReadKey();  
  15.         }  
  16.         static void SempStart(object id)  
  17.         {  
  18.             Console.WriteLine(id + "-->>Wants to Get Enter");  
  19.             try  
  20.             {  
  21.                 obj.WaitOne();  
  22.                 Console.WriteLine(" Success: " + id + " is in!");     
  23.                 Thread.Sleep(2000);  
  24.                 Console.WriteLine(id + "<<-- is Evacuating");  
  25.             }  
  26.             finally  
  27.             {  
  28.                 obj.Release();  
  29.             }  
  30.         }  
  31.     }  
  32. }  
While we run this application, 2 semaphores are immediately created and the others wait because we have created 5 threads. So 3 are in the waiting state. The movement, any one of thread released the rest of created one by one as following.
 
Multithreading with .NET 
 

Summary

 
This article explained how to code multithreaded applications using the System.Threading Namespace. Multithreading in your application can cause concurrency issues such as race conditions and deadlocks.
 
Finally this article discussed the various techniques for synchronization such as Locks, Mutexes, and Semaphores to handle concurrency problems and protect thread sensitive blocks of code for smooth execution and accurate results.