Threads In C#

Threading in C# - Threads in .NET are the basic unit of execution within a process. Learn how to use threads in .NET and C# and create your first threading app.

When a new application starts on Windows, it creates a process for the application with a process id and some resources are allocated to this new process. Every process contains at least one primary thread which takes care of the entry point of the application execution. A single thread can have only one path of execution but as mentioned earlier, sometimes you may need multiple paths of execution and that is where threads play a role.

In .NET Core, the common language runtime (CLR) plays a major role in creating and managing threads lifecycle. In a new .NET Core application, the CLR creates a single foreground thread to execute application code via the Main method. This thread is called primary or main thread. Along with this main thread, a process can create one or more threads to execute a portion of the code. Additionally, a program can use the ThreadPool class to execute code on worker threads that are managed by the CLR.

A C# program is single threaded by design. That means, only one path of the code is executed at a time by the main or primary thread. The entry point of a C# program starts in the Main method and that is the path of the primary thread.

Why threads? 

The Main method is the entry point of a C# program. The code in the Main method is executed in a linear fashion in a single, primary thread.

Let’s take an example of code in Listing 1. 

  1. using System;  
  2.   
  3. class Program  
  4. {  
  5.     // This is the entry point of a C# program  
  6.     static void Main(string[] args)  
  7.     {  
  8.         // Main execution starts here  
  9.         Console.WriteLine("Main thread starts here.");  
  10.   
  11.         // This method takes 4 seconds to finish.  
  12.         Program.DoSomeHeavyLifting();      
  13.   
  14.         // This method doesn't take anytime at all.  
  15.         Program.DoSomething();  
  16.   
  17.         // Execution ends here  
  18.         Console.WriteLine("Main thread ends here.");  
  19.         Console.ReadKey();  
  20.     }  
  21.   
  22.     public static void DoSomeHeavyLifting()  
  23.     {  
  24.         Console.WriteLine("I'm lifting a truck!!");  
  25.         Thread.Sleep(1000);  
  26.         Console.WriteLine("Tired! Need a 3 sec nap.");  
  27.         Thread.Sleep(1000);  
  28.         Console.WriteLine("1....");  
  29.         Thread.Sleep(1000);  
  30.         Console.WriteLine("2....");  
  31.         Thread.Sleep(1000);  
  32.         Console.WriteLine("3....");  
  33.         Console.WriteLine("I'm awake.");  
  34.     }  
  35.     public static void DoSomething()  
  36.     {  
  37.         Console.WriteLine("Hey! DoSomething here!");  
  38.         for (int i = 0; i < 20; i++)  
  39.             Console.Write($"{i} ");  
  40.         Console.WriteLine();  
  41.         Console.WriteLine("I'm done.");  
  42.     }  
  43. }  

Listing 1.

In Listing 1, the DoSomeHeavyLifting is the first method being called that takes about 4 seconds. After that, the DoSomething method is called. In this case, the DoSomething method has to wait until the DoSomeHeavyLifting method is executed. What if there is a function that needs even longer background work? For example, creating a report and printing it. How about a Windows application that has a method that takes longer and the user has to wait to do anything else?

Multiple threads or multithreading is the solution for this problem. Multithreading, or simply threading, allows us to create secondary threads that may be used to execute time-consuming background tasks and leave the primary thread available to the main program. This makes an application more responsive and use friendly.

Now let’s replace DoSomeHeavyLifting method call in the Main method with a new code that creates a new thread. Replace the following code,

  1. Program.DoSomeHeavyLifting();  

With the following code,

  1. // Create a thread and call a background method   
  2. Thread backgroundThread = new Thread(new ThreadStart(Program.DoSomeHeavyLifting));  
  3. // Start thread  
  4. backgroundThread.Start();  

In this code, we have created a new thread object using the Thread class that takes a ThreadStart delegate as a parameter with method, that is executed in the background. Thread.Start() methods stars a new thread. This new thread is called a worker thread or a secondary thread.

Now, no matter how long the worker thread method takes, the main thread code will be executed side by side.

The new Main method is listed in Listing 2.

  1. static void Main(string[] args)  
  2. {  
  3.     // Main execution starts here  
  4.     Console.WriteLine("Main thread starts here.");  
  5.   
  6.     // Create a thread   
  7.     Thread backgroundThread = new Thread(new ThreadStart(Program.DoSomeHeavyLifting));  
  8.     // Start thread  
  9.     backgroundThread.Start();          
  10.   
  11.     // This method doesn't take anytime at all.  
  12.     Program.DoSomething();  
  13.   
  14.     // Execution ends here  
  15.     Console.WriteLine("Main thread ends here.");  
  16.     Console.ReadKey();  
  17. }  

Listing 2.

Now, run the program and you will see that there is no delay in executing DoSomething method.

Threads, Resources, and Performance 

Keep in mind, creating more threads is not related to processing speed or performance. All threads share the same processor and resources a machine has. In cases of multiple threads, the thread scheduler with the help of the operating system schedules threads and allocates a time for each thread. But in a single processor machine, only one thread can execute at a time. The rest of the threads have to wait until the processor becomes available. Creating more than a few threads on a single processor machine may create a resource bottleneck if not managed properly. Ideally, you want a couple of threads per processor. In the case of dual core processors, having 4 threads is ideal. In the case of a quad core processor, you can create up to 8 threads without noticing any issues.

Managing thread resources is also very important for resource hungry apps. If you’ve some background IO processing and some background database operations, you should manage it so that each thread works with separate resources.

Similar to a process, threads also run within their own boundaries but they can communicate with each other, share resources, and pass data among them.

Create and start a thread in C# 

The Thread class represents a thread and provides functionality to create and manage a thread’s lifecycle, and its properties, such as status, priority, state.

The Thread class is defined in the System.Threading namespace that must be imported before you can use any threading related types.

  1. using System.Threading;  

The Thread constructor takes a ThreadStart delegate as a parameter and creates a new thread. The parameter of the ThreadStart is the method that is executed by the new thread. Once a thread it created, it needs to call the Start method to actually start the thread.

The following code snippet creates a new thread, workerThread, that will execute code in the Print method.

  1. // Create a secondary thread by passing a ThreadStart delegate  
  2. Thread workerThread = new Thread(new ThreadStart(Print));  
  3. // Start secondary thread  
  4. workerThread.Start();  

The Print method is listed below that can be used to execute code to do some background or foreground work.

  1. static void Print()  
  2. {  
  3.       
  4. }  

Let’s try it.

Open Visual Studio. Create a new .NET Core console project. Delete all code and copy and paste (or type) the code in Listing 1.

  1. using System;  
  2. using System.Threading;  
  3.   
  4. class Program  
  5. {  
  6.     static void Main()  
  7.     {  
  8.         // Create a secondary thread by passing a ThreadStart delegate  
  9.         Thread workerThread = new Thread(new ThreadStart(Print));  
  10.         // Start secondary thread  
  11.         workerThread.Start();  
  12.   
  13.         // Main thread : Print 1 to 10 every 0.2 second.   
  14.         // Thread.Sleep method is responsible for making the current thread sleep  
  15.         // in milliseconds. During its sleep, a thread does nothing.  
  16.         for (int i=0; i< 10; i++)  
  17.         {  
  18.             Console.WriteLine($"Main thread: {i}");  
  19.             Thread.Sleep(200);  
  20.         }         
  21.   
  22.         Console.ReadKey();  
  23.     }  
  24.   
  25.     /// <summary>  
  26.     ///  This code is executed by a secondary thread  
  27.     /// </summary>  
  28.     static void Print()  
  29.     {  
  30.         for (int i = 11; i < 20; i++)  
  31.         {  
  32.             Console.WriteLine($"Worker thread: {i}");  
  33.             Thread.Sleep(1000);  
  34.         }  
  35.     }  
  36. }  

Listing 1.

The code of Listing 1, the main thread prints 1 to 10 after every 0.2 seconds. The secondary thread prints from 11 to 20 after every 1.0 second. We’re using the delay for demo purposes, so you can see live how two threads execute code in parallel.

The output looks like Figure 1, where you can see the main thread prints a number every 0.2 second while the secondary thread prints a number every 1.0 second and both threads run in parallel.

Threading In C#
Figure 1. 

Thread Name, Thread Priority, and Thread State 

We can set a thread’s name and its priority by using Name and Priority properties. The following code snippet sets the Name of a thread, 

  1. workerThread.Name = "Hard Worker";  

The Priority property is used to get and set a thread’s priority. An operating system executes high priority threads before low priority threads. The Priority property is of ThreadPriority enum type. ThreadPriority values are Highest, AboveNormal, Normal, BelowNormal, and Lowest. The following code snippet sets a thread priority to highest.

  1. workerThread.Priority = ThreadPriority.Highest;  

Thread state 

During its lifecycle, each thread goes through a state. The following diagram illustrates various threads states. Thread always states in Unstrated state and the only transition is to start the thread and state changes to Running. A running thread has three transitions, Suspended,  Sleep, and AbortRequested. More states are explained in the table below.

Threading In C#

The ThreadState property returns the current state of a thread. You cannot set a thread’s state using this property. Table 1 describes various thread states.

State Description
A thread is created Unstarted
Another thread calls the Thread.Start method on the new thread, and the call returns. The Start method does not return until the new thread has started running. There is no way to know at what point the new thread will start running, during the call to Start. Running
The thread calls Sleep WaitSleepJoin
The thread calls Wait on another object. WaitSleepJoin
The thread calls Join on another thread. WaitSleepJoin
Another thread calls Interrupt Running
Another thread calls Suspend SuspendRequested
The thread responds to a Suspend request. Suspended
Another thread calls Resume Running
Another thread calls Abort AbortRequested
The thread responds to a Abort request. Stopped
A thread is terminated. Stopped
The thread state includes AbortRequested and the thread is now dead, but its state has not yet changed to Stopped. Aborted
If thread is a background thread Background

Table 1.

A thread can be in more than one state at a time.

The following code snippet checks if a thread state is Running, then aborts it.

  1. // Check thread state   
  2. if (workerThread.ThreadState == ThreadState.Running)  
  3. {  
  4.     // if running, abort it.  
  5.     workerThread.Abort();  
  6. }  

Getting the current thread in C# 

The Thread.CurrentThread returns the current thread that is executing the current code. The following code snippet prints the current thread’s properties such as its Id, priority, name, and culture, 

  1. Thread currentThread = Thread.CurrentThread;  
  2. Thread currentThread = Thread.CurrentThread;  
  3. Console.WriteLine("Thread Id {0}: ", currentThread.ManagedThreadId);  
  4. Console.WriteLine("Is thread background: {0}", currentThread.IsBackground);  
  5. Console.WriteLine("Priority: {0}", currentThread.Priority);  
  6. Console.WriteLine("Culture: {0}", currentThread.CurrentCulture.Name);  
  7. Console.WriteLine("UI Culture: {0}", currentThread.CurrentUICulture.Name);  
  8. Console.WriteLine();  

Thread inherits culture and UI culture from current system.

Foreground and background threads in C# 

There are two types of threads, foreground and background. Besides the main application thread, all threads created by calling a Thread class constructor are foreground threads.

Background threads are the threads that are created and used from the ThreadPool, which is a pool of worker threads maintained by the runtime. Background threads are identical to foreground threads with one exception: A background thread does not keep a process running if all foreground threads have terminated. Once all foreground threads have been stopped, the runtime stops all background threads and shuts down.

You can change a thread to execute in the background by setting the IsBackground property at any time. Background threads are useful for any operation that should continue as long as an application is running but should not prevent the application from terminating, such as monitoring file system changes or incoming socket connections.

Pause and abort threads 

Thread.Sleep method can be used to pause the current thread for a fixed period of time in milliseconds. The following code snippet pauses a thread for 1 second. 

  1. Thread.Sleep(1000);  

The Abort method is used to abort a thread. Make sure you call IsAlive before Abort.

  1. if (workerThread.IsAlive)  
  2.     workerThread.Abort();   

Passing data to a worker thread 

It is often required that some data needs to be passed to a worker thread from the main thread. The Thread.Start method has an overloaded form that takes an object type parameter.

The Thread class constructor takes either a ThreadStart or a ParemeterizedThreadStart delegate. The ParemeterizedThreadStart delegate is used when you need to pass to the thread.

Let’s look at the following code snippet where the Print class has two methods, PrintJob and PrintPerson. We want to execute these methods in two separate worker threads. As you can clearly see, both methods take a parameter of object type. 

  1. public class Print  
  2. {  
  3.     public void PrintJob(object data)  
  4.     {  
  5.         Console.WriteLine("Background PrintJob started.");  
  6.         Thread.Sleep(1000);  
  7.         Console.WriteLine("PrintJob still printing...");  
  8.         Console.WriteLine($"Data: {data}");  
  9.         Thread.Sleep(1000);  
  10.         Console.WriteLine("PrintJob finished.");  
  11.     }  
  12.   
  13.     public void PrintPerson(object data)  
  14.     {  
  15.         Console.WriteLine("Background PrintPerson started.");  
  16.         Thread.Sleep(1000);  
  17.         Console.WriteLine("PrintPerson still printing...");  
  18.         Person p = (Person)data;  
  19.         Console.WriteLine($"Person {p.Name} is a {p.Sex} of {p.Age} age.");  
  20.         Thread.Sleep(1000);  
  21.         Console.WriteLine("PrintPerson finished.");  
  22.     }  
  23. }  

Now, we will see how to pass an object type from the main thread to a worker thread.

The following code snippet starts a new worker thread that executes the PrintJob method. Since the PrintJob is a ParemeterizedThreadStart delegate, the Start method takes a parameter. In this case, I pass a string as a parameter.

  1. // Create a thread with a ParemeterizedThreadStart  
  2. Print p = new Print();  
  3. Thread workerThread = new Thread(p.PrintJob);  
  4. // Start thread with a parameter  
  5. workerThread.Start("Some data");  

The PrintPerson method of the Print class takes a complex object of type Person. In the following code snippet, I create a Person class and pass it as a parameter of the Thread.Start method.

  1. // Pass a class object to a worker thread  
  2. Person mahesh = new Person("Mahesh Chand", 40, "Male");  
  3. Thread workerThread2 = new Thread(p.PrintPerson);  
  4. workerThread2.Start(mahesh);  

The ParameterizedThreadStart delegate supports only a single parameter. To pass complex or multiple data items, you can pass an Array, a collection type, or a tuple type.

Thread Pool 

Creation and destruction of new threads comes with a cost and it affects an application’s performance. Threads can also be blocked or go into sleep or other unresolved states. If your app doesn’t distribute workload properly, worker threads may spend the majority of their time sleeping. This is where thread pool comes in handy.

A thread pool is a pool of worker threads that have already been created and are available for apps to use them as needed. Once thread pool threads finish executing their tasks, they go back to the pool.

.NET provides a managed thread pool via the ThreadPool class that is managed by the system. As a developer, we don’t need to deal with the thread management overhead. For any short background tasks, the managed thread pool is a better choice than creating and managing your own threads. Thread pool threads are good for background process only and not recommended for foreground threads. There is only one thread pool per process.

Use of thread pool is not recommended when,

  • You need to prioritize a thread
  • Thread is a foreground thread.
  • You have tasks that cause the thread to block for long periods of time. The thread pool has a maximum number of threads, so a large number of blocked thread pool threads might prevent tasks from starting.
  • You need to place threads into a single-threaded apartment. All ThreadPool threads are in the multithreaded apartment.
  • You need to have a stable identity associated with the thread, or to dedicate a thread to a task. 

ThreadPool Thread 

The ThreadPool class has several static methods, including the QueueUserWorkItem, that is responsible for calling a thread pool worker thread when it is available. If no worker thread is available in the thread pool, it waits until the thread becomes available.

The QueueWorkItem method takes a procedure that executes in the background.

  1. ThreadPool.QueueUserWorkItem(BackgroundTask);  

Here is a complete example of how to call a worker thread from thread pool to execute a method in background.

  1. using System;  
  2. using System.Threading;  
  3.   
  4. class ThreadPoolSample  
  5. {  
  6.     // Background task   
  7.     static void BackgroundTask(Object stateInfo)  
  8.     {  
  9.         Console.WriteLine("Hello! I'm a worker from ThreadPool");  
  10.         Thread.Sleep(1000);          
  11.     }  
  12.   
  13.     static void Main(string[] args)  
  14.     {  
  15.         // Use ThreadPool for a worker thread        
  16.         ThreadPool.QueueUserWorkItem(BackgroundTask);  
  17.         Console.WriteLine("Main thread does some work, then sleeps.");  
  18.         Thread.Sleep(500);  
  19.         Console.WriteLine("Main thread exits.");  
  20.         Console.ReadKey();  
  21.     }  
  22. }  

You can also pass values to a background method via the QueueWorkItem method. The second parameter of the method is an object that can be any object you would like to pass to your background procedure.

Let’s assume we have a Person class with the following members.

  1. // Create a Person class  
  2. public class Person  
  3. {  
  4.     public string Name { get; set; }  
  5.     public int Age { get; set; }  
  6.     public string Sex { get; set; }  
  7.   
  8.     public Person(string name, int age, string sex)  
  9.     {  
  10.         this.Name = name;  
  11.         this.Age = age;  
  12.         this.Sex = sex;  
  13.     }  
  14. }  

We can create an object of Person type and pass it to the QueueUserWorkItem method.

  1. // Create an object and pass it to ThreadPool worker thread  
  2. Person p = new Person("Mahesh Chand", 40, "Male");  
  3. ThreadPool.QueueUserWorkItem(BackgroundTaskWithObject, p);  

And in the worker method, we can extract values from the object and use it. In the following example, I read back the Person.Name and it displays on the console.

  1. static void BackgroundTaskWithObject(Object stateInfo)  
  2. {  
  3.     Person data = (Person)stateInfo;          
  4.     Console.WriteLine($"Hi {data.Name} from ThreadPool.");  
  5.     Thread.Sleep(1000);  
  6. }  

The complete code is listed in Listing 2.

  1. using System;  
  2. using System.Threading;  
  3.   
  4. class ThreadPoolSample  
  5. {  
  6.     // Background task   
  7.     static void BackgroundTask(Object stateInfo)  
  8.     {  
  9.         Console.WriteLine("Hello! I'm a worker from ThreadPool");  
  10.         Thread.Sleep(1000);          
  11.     }  
  12.   
  13.     static void BackgroundTaskWithObject(Object stateInfo)  
  14.     {  
  15.         Person data = (Person)stateInfo;          
  16.         Console.WriteLine($"Hi {data.Name} from ThreadPool.");  
  17.         Thread.Sleep(1000);  
  18.     }  
  19.     static void Main(string[] args)  
  20.     {  
  21.         // Create an object and pass it to ThreadPool worker thread  
  22.         Person p = new Person("Mahesh Chand", 40, "Male");  
  23.         ThreadPool.QueueUserWorkItem(BackgroundTaskWithObject, p);  
  24.   
  25.         Console.ReadKey();  
  26.     }  
  27.   
  28.         // Create a Person class  
  29.         public class Person  
  30.         {  
  31.             public string Name { get; set; }  
  32.             public int Age { get; set; }  
  33.             public string Sex { get; set; }  
  34.   
  35.             public Person(string name, int age, string sex)  
  36.             {  
  37.                 this.Name = name;  
  38.                 this.Age = age;  
  39.                 this.Sex = sex;  
  40.             }  
  41.         }  
  42. }  

Listing 2. 

Maximum and Minimum Thread Pool Threads 

Thread pool size is the number of threads available in a thread pool. The thread pool provides new worker threads or I/O completion threads on demand until it reaches the minimum for each category. By default, the minimum number of threads is set to the number of processors on a system. When the minimum is reached, the thread pool can create additional threads in that category or wait until some tasks complete. The thread pool creates and destroys threads to optimize throughput, which is defined as the number of tasks that complete per unit of time. Too few threads might not make optimal use of available resources, whereas too many threads could increase resource contention.

ThreadPool.GetAvailalbeThreads returns the number of threads that are available currently in a pool. It is the number of maximum threads minus currently active threads.

  1. int workers, ports;  
  2. // Get available threads  
  3. ThreadPool.GetAvailableThreads(out workers, out ports);  
  4. Console.WriteLine($"Availalbe worker threads: {workers} ");  
  5. Console.WriteLine($"Available completion port threads: {ports}");  

ThreadPool.GetMaxThreads and ThreadPool.GetMinThreads returns the maximum and minimum threads available in a thread pool.

  1. int workers, ports;  
  2. // Get maximum number of threads  
  3. ThreadPool.GetMaxThreads(out workers, out ports);  
  4. Console.WriteLine($"Maximum worker threads: {workers} ");  
  5. Console.WriteLine($"Maximum completion port threads: {ports}");  

ThreadPool.SetMaxThreads and ThreadPool.SetMinThreads are used to set maximum and minimum number of threads on demand as needed in a thread pool. By default, the minimum number of threads is set to the number of processors on a system.

  1. int workers, ports;  
  2. // Set minimum threads  
  3. int minWorker, minIOC;  
  4. ThreadPool.GetMinThreads(out minWorker, out minIOC);  
  5. ThreadPool.SetMinThreads(4, minIOC);  

The complete example is listed in Listing 3.

  1. using System;  
  2. using System.Threading;  
  3.   
  4. class ThreadPoolSample  
  5. {  
  6.     // Background task   
  7.     static void BackgroundTask(Object stateInfo)  
  8.     {  
  9.         Console.WriteLine("Hello! I'm a worker from ThreadPool");  
  10.         Thread.Sleep(1000);          
  11.     }  
  12.   
  13.     static void BackgroundTaskWithObject(Object stateInfo)  
  14.     {  
  15.         Person data = (Person)stateInfo;          
  16.         Console.WriteLine($"Hi {data.Name} from ThreadPool.");  
  17.         Thread.Sleep(1000);  
  18.     }  
  19.     static void Main(string[] args)  
  20.     {  
  21.         // Use ThreadPool for a worker thread        
  22.         ThreadPool.QueueUserWorkItem(BackgroundTask);  
  23.         Console.WriteLine("Main thread does some work, then sleeps.");  
  24.         Thread.Sleep(500);  
  25.      
  26.         // Create an object and pass it to ThreadPool worker thread  
  27.         Person p = new Person("Mahesh Chand", 40, "Male");  
  28.         ThreadPool.QueueUserWorkItem(BackgroundTaskWithObject, p);  
  29.   
  30.         int workers, ports;  
  31.   
  32.         // Get maximum number of threads  
  33.         ThreadPool.GetMaxThreads(out workers, out ports);  
  34.         Console.WriteLine($"Maximum worker threads: {workers} ");  
  35.         Console.WriteLine($"Maximum completion port threads: {ports}");  
  36.       
  37.         // Get available threads  
  38.         ThreadPool.GetAvailableThreads(out workers, out ports);  
  39.         Console.WriteLine($"Availalbe worker threads: {workers} ");  
  40.         Console.WriteLine($"Available completion port threads: {ports}");  
  41.   
  42.         // Set minimum threads  
  43.         int minWorker, minIOC;  
  44.         ThreadPool.GetMinThreads(out minWorker, out minIOC);  
  45.         ThreadPool.SetMinThreads(4, minIOC);  
  46.   
  47.         // Get total number of processes availalbe on the machine  
  48.         int processCount = Environment.ProcessorCount;  
  49.         Console.WriteLine($"No. of processes available on the system: {processCount}");  
  50.   
  51.         // Get minimum number of threads  
  52.         ThreadPool.GetMinThreads(out workers, out ports);  
  53.         Console.WriteLine($"Minimum worker threads: {workers} ");  
  54.         Console.WriteLine($"Minimum completion port threads: {ports}");  
  55.   
  56.         Console.ReadKey();  
  57.     }  
  58.   
  59.         // Create a Person class  
  60.         public class Person  
  61.         {  
  62.             public string Name { get; set; }  
  63.             public int Age { get; set; }  
  64.             public string Sex { get; set; }  
  65.   
  66.             public Person(string name, int age, string sex)  
  67.             {  
  68.                 this.Name = name;  
  69.                 this.Age = age;  
  70.                 this.Sex = sex;  
  71.             }  
  72.         }  
  73. }  

Listing 3.

Summary 

This article is an introduction to threading in .NET. In this article, we learned why threading is useful and how we can create and manage worker threads to execute background tasks. We also learned about managed thread pool and how to use it to execute background tasks.

References