Threads In C#

Introduction

C# threading allows developers to create multiple threads in C# and .NET. In this article and code example, 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.

Common language runtime

In .NET Core, the common language runtime (CLR) plays a major role in creating and managing threads' lifecycles. 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 the primary thread. Along with this main thread, a process can create one or more threads to execute a portion of the code. A program can also use the ThreadPool class to execute code on worker threads that the CLR manages.

A C# program is single-threaded by design. That means only one path of the code is executed at a time by the primary thread. The entry point of a C# program starts in the Main method, which 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. 

using System;  
  
class Program  
{  
    // This is the entry point of a C# program  
    static void Main(string[] args)  
    {  
        // Main execution starts here  
        Console.WriteLine("Main thread starts here.");  
  
        // This method takes 4 seconds to finish.  
        Program.DoSomeHeavyLifting();      
  
        // This method doesn't take anytime at all.  
        Program.DoSomething();  
  
        // Execution ends here  
        Console.WriteLine("Main thread ends here.");  
        Console.ReadKey();  
    }  
  
    public static void DoSomeHeavyLifting()  
    {  
        Console.WriteLine("I'm lifting a truck!!");  
        Thread.Sleep(1000);  
        Console.WriteLine("Tired! Need a 3 sec nap.");  
        Thread.Sleep(1000);  
        Console.WriteLine("1....");  
        Thread.Sleep(1000);  
        Console.WriteLine("2....");  
        Thread.Sleep(1000);  
        Console.WriteLine("3....");  
        Console.WriteLine("I'm awake.");  
    }  
    public static void DoSomething()  
    {  
        Console.WriteLine("Hey! DoSomething here!");  
        for (int i = 0; i < 20; i++)  
            Console.Write($"{i} ");  
        Console.WriteLine();  
        Console.WriteLine("I'm done.");  
    }  
}  

Listing 1.

In Listing 1, DoSomeHeavyLifting is the first method, which 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 more extended background work? For example, creating a report and printing it. How about a Windows application with a more extended method, 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 user-friendly.

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

Program.DoSomeHeavyLifting();  

With the following code,

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

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

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.

static void Main(string[] args)  
{  
    // Main execution starts here  
    Console.WriteLine("Main thread starts here.");  
  
    // Create a thread   
    Thread backgroundThread = new Thread(new ThreadStart(Program.DoSomeHeavyLifting));  
    // Start thread  
    backgroundThread.Start();          
  
    // This method doesn't take anytime at all.  
    Program.DoSomething();  
  
    // Execution ends here  
    Console.WriteLine("Main thread ends here.");  
    Console.ReadKey();  
}  

Listing 2.

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

Threads, Resources, and Performance 

Remember, creating more threads is not related to processing speed or performance. All lines share the same processor and resources a machine has. In cases of multiple threads, the thread scheduler, with the operating system's help, schedules threads and allocates a time for each thread. But in a single processor machine, only one thread can execute simultaneously. The rest of the threads have to wait until the processor becomes available. If not managed properly, creating more than a few threads on a single processor machine may create a resource bottleneck. 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 background database operations, you should manage it so that each thread works with different resources.

Like a process, threads also run within their own boundaries but 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, and state.

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

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 executed by the new thread. Once a thread is created, it needs to call the Start method to start the thread.

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

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

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

static void Print()  
{  
      
}  

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.

using System;  
using System.Threading;  
  
class Program  
{  
    static void Main()  
    {  
        // Create a secondary thread by passing a ThreadStart delegate  
        Thread workerThread = new Thread(new ThreadStart(Print));  
        // Start secondary thread  
        workerThread.Start();  
  
        // Main thread : Print 1 to 10 every 0.2 second.   
        // Thread.Sleep method is responsible for making the current thread sleep  
        // in milliseconds. During its sleep, a thread does nothing.  
        for (int i=0; i< 10; i++)  
        {  
            Console.WriteLine($"Main thread: {i}");  
            Thread.Sleep(200);  
        }         
  
        Console.ReadKey();  
    }  
  
    /// <summary>  
    ///  This code is executed by a secondary thread  
    /// </summary>  
    static void Print()  
    {  
        for (int i = 11; i < 20; i++)  
        {  
            Console.WriteLine($"Worker thread: {i}");  
            Thread.Sleep(1000);  
        }  
    }  
}  

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 seconds. We're using the delay for demo purposes, so you can see how two threads execute code in parallel.

The output looks like Figure 1, where the main thread prints a number every 0.2 seconds while the secondary thread prints a number every 1.0 seconds 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 priority using Name and Priority properties. The following code snippet sets the Name of a thread, 

workerThread.Name = "Hard Worker";  

An operating system executes high-priority threads before low-priority threads. The Priority property is used to get and set a thread's priority. 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 the highest.

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. During the call to Start, there is no way to know at what point the new thread will start running. The Start method does not return until the new thread has started running. 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 an Abort request. Stopped
A thread is terminated. Stopped
The thread state includes AbortRequested, which is now dead, but its state has not yet changed to Stopped. Aborted
If a 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.

// Check thread state   
if (workerThread.ThreadState == ThreadState.Running)  
{  
    // if running, abort it.  
    workerThread.Abort();  
}  

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, 

Thread currentThread = Thread.CurrentThread;  
Thread currentThread = Thread.CurrentThread;  
Console.WriteLine("Thread Id {0}: ", currentThread.ManagedThreadId);  
Console.WriteLine("Is thread background: {0}", currentThread.IsBackground);  
Console.WriteLine("Priority: {0}", currentThread.Priority);  
Console.WriteLine("Culture: {0}", currentThread.CurrentCulture.Name);  
Console.WriteLine("UI Culture: {0}", currentThread.CurrentUICulture.Name);  
Console.WriteLine();  

Thread inherits culture and UI culture from the 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 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 are 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 anytime. Background threads are useful for any operation that should continue as long as an application runs 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 pause the current thread for a fixed period in milliseconds. The following code snippet pauses a thread for 1 second. 

Thread.Sleep(1000);  

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

if (workerThread.IsAlive)  
    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. As you can see, both methods take a parameter of the object type. We want to execute these methods in two separate worker threads. 

public class Print  
{  
    public void PrintJob(object data)  
    {  
        Console.WriteLine("Background PrintJob started.");  
        Thread.Sleep(1000);  
        Console.WriteLine("PrintJob still printing...");  
        Console.WriteLine($"Data: {data}");  
        Thread.Sleep(1000);  
        Console.WriteLine("PrintJob finished.");  
    }  
  
    public void PrintPerson(object data)  
    {  
        Console.WriteLine("Background PrintPerson started.");  
        Thread.Sleep(1000);  
        Console.WriteLine("PrintPerson still printing...");  
        Person p = (Person)data;  
        Console.WriteLine($"Person {p.Name} is a {p.Sex} of {p.Age} age.");  
        Thread.Sleep(1000);  
        Console.WriteLine("PrintPerson finished.");  
    }  
}  

We will now 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.

// Create a thread with a ParemeterizedThreadStart  
Print p = new Print();  
Thread workerThread = new Thread(p.PrintJob);  
// Start thread with a parameter  
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 Thread parameter.Start method.

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

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

Thread Pool 

Creating and destroying new threads come with a cost that 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 most of their time sleeping. This is where the 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 as needed. Once thread pool threads finish executing their tasks, they return to the pool.

.NET provides a managed thread pool via the ThreadPool class that the system manages. As developers, we don't need to deal with thread management overhead. For any short background tasks, the managed thread pool is better than creating and managing your own threads. Thread pool threads are suitable only for the background process and are 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
  • The thread is a foreground thread.
  • You have tasks that cause the thread to block for long periods. 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. 

Learn more about Thread Pool in C# and .NET Core

ThreadPool Thread 

The ThreadPool class has several static methods, including the QueueUserWorkItem, which 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.

ThreadPool.QueueUserWorkItem(BackgroundTask);  

Here is a complete example of calling a worker thread from the thread poocallinge a method in the background.

using System;  
using System.Threading;  
  
class ThreadPoolSample  
{  
    // Background task   
    static void BackgroundTask(Object stateInfo)  
    {  
        Console.WriteLine("Hello! I'm a worker from ThreadPool");  
        Thread.Sleep(1000);          
    }  
  
    static void Main(string[] args)  
    {  
        // Use ThreadPool for a worker thread        
        ThreadPool.QueueUserWorkItem(BackgroundTask);  
        Console.WriteLine("Main thread does some work, then sleeps.");  
        Thread.Sleep(500);  
        Console.WriteLine("Main thread exits.");  
        Console.ReadKey();  
    }  
}  

You can also pass values to a background method via the QueueWorkItem method. The method's second parameter 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.

// Create a Person class  
public class Person  
{  
    public string Name { get; set; }  
    public int Age { get; set; }  
    public string Sex { get; set; }  
  
    public Person(string name, int age, string sex)  
    {  
        this.Name = name;  
        this.Age = age;  
        this.Sex = sex;  
    }  
}  

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

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

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

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

The complete code is listed in Listing 2.

using System;  
using System.Threading;  
  
class ThreadPoolSample  
{  
    // Background task   
    static void BackgroundTask(Object stateInfo)  
    {  
        Console.WriteLine("Hello! I'm a worker from ThreadPool");  
        Thread.Sleep(1000);          
    }  
  
    static void BackgroundTaskWithObject(Object stateInfo)  
    {  
        Person data = (Person)stateInfo;          
        Console.WriteLine($"Hi {data.Name} from ThreadPool.");  
        Thread.Sleep(1000);  
    }  
    static void Main(string[] args)  
    {  
        // Create an object and pass it to ThreadPool worker thread  
        Person p = new Person("Mahesh Chand", 40, "Male");  
        ThreadPool.QueueUserWorkItem(BackgroundTaskWithObject, p);  
  
        Console.ReadKey();  
    }  
  
        // Create a Person class  
        public class Person  
        {  
            public string Name { get; set; }  
            public int Age { get; set; }  
            public string Sex { get; set; }  
  
            public Person(string name, int age, string sex)  
            {  
                this.Name = name;  
                this.Age = age;  
                this.Sex = sex;  
            }  
        }  
}  

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 are complete. The thread pool creates and destroys threads to optimize throughput, defined as the number of tasks completed 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 currently available in a pool. It is the number of maximum threads minus currently active threads.

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

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

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

ThreadPool.SetMaxThreads and ThreadPool.SetMinThreads are used to set a 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.

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

The complete example is listed in Listing 3.

using System;  
using System.Threading;  
  
class ThreadPoolSample  
{  
    // Background task   
    static void BackgroundTask(Object stateInfo)  
    {  
        Console.WriteLine("Hello! I'm a worker from ThreadPool");  
        Thread.Sleep(1000);          
    }  
  
    static void BackgroundTaskWithObject(Object stateInfo)  
    {  
        Person data = (Person)stateInfo;          
        Console.WriteLine($"Hi {data.Name} from ThreadPool.");  
        Thread.Sleep(1000);  
    }  
    static void Main(string[] args)  
    {  
        // Use ThreadPool for a worker thread        
        ThreadPool.QueueUserWorkItem(BackgroundTask);  
        Console.WriteLine("Main thread does some work, then sleeps.");  
        Thread.Sleep(500);  
     
        // Create an object and pass it to ThreadPool worker thread  
        Person p = new Person("Mahesh Chand", 40, "Male");  
        ThreadPool.QueueUserWorkItem(BackgroundTaskWithObject, p);  
  
        int workers, ports;  
  
        // Get maximum number of threads  
        ThreadPool.GetMaxThreads(out workers, out ports);  
        Console.WriteLine($"Maximum worker threads: {workers} ");  
        Console.WriteLine($"Maximum completion port threads: {ports}");  
      
        // Get available threads  
        ThreadPool.GetAvailableThreads(out workers, out ports);  
        Console.WriteLine($"Availalbe worker threads: {workers} ");  
        Console.WriteLine($"Available completion port threads: {ports}");  
  
        // Set minimum threads  
        int minWorker, minIOC;  
        ThreadPool.GetMinThreads(out minWorker, out minIOC);  
        ThreadPool.SetMinThreads(4, minIOC);  
  
        // Get total number of processes availalbe on the machine  
        int processCount = Environment.ProcessorCount;  
        Console.WriteLine($"No. of processes available on the system: {processCount}");  
  
        // Get minimum number of threads  
        ThreadPool.GetMinThreads(out workers, out ports);  
        Console.WriteLine($"Minimum worker threads: {workers} ");  
        Console.WriteLine($"Minimum completion port threads: {ports}");  
  
        Console.ReadKey();  
    }  
  
        // Create a Person class  
        public class Person  
        {  
            public string Name { get; set; }  
            public int Age { get; set; }  
            public string Sex { get; set; }  
  
            public Person(string name, int age, string sex)  
            {  
                this.Name = name;  
                this.Age = age;  
                this.Sex = sex;  
            }  
        }  
}  

Listing 3.

Summary 

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

References 


Similar Articles
Mindcracker
Founded in 2003, Mindcracker is the authority in custom software development and innovation. We put best practices into action. We deliver solutions based on consumer and industry analysis.