C# Thread Basics

Learn the basics of C# Thread. This code example explains how to create a Thread in C# and .NET Core.

Introduction to Threads In C# and .NET

 
A computer operating system runs dozens, if not hundreds, of processes simultaneously and each process is responsible for some task. Let’s look at the Task Manager running on my machine that shows the processes running on my machine. See Figure 1.
 
Threading In C#
Figure 1.

As you can see in Figure 1, there are several processes running on my machine and each process has resource allocation and their usages. The resources include CPU, memory, disk space, network, and IO.

Each computer has finite resources such as CPU, memory, disk space, network, and IO. A computer runs several applications simultaneously. Some of these applications are operating-system related, some are services, and some are user applications. Figure 1 categorizes processes in three different categories - Apps, Background processes, and Windows processes. Apps are the foreground apps running on the device. Background processes runs in the background for various applications installed on the device. Windows processes are the processes required to run Windows operating system, such as checking notifications, emails, app updates, resource management, and so on.

Each process on an operating system is contained within its own boundary and the operating system assigns limited resources to each process so it does not affect other processes.

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.

.NET Core and Threads

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 a primary or main thread. Along with this main thread, a process can create one or more threads to execute a portion of the code that is not executed by the main thread. 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 multiple threads

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

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

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

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 time and a 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 let the primary thread be 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 starts 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.     // Main execution starts here  
  3.     Console.WriteLine("Main thread starts here.");  
  4.     // Create a thread  
  5.     Thread backgroundThread = new Thread(new ThreadStart(Program.DoSomeHeavyLifting));  
  6.     // Start thread  
  7.     backgroundThread.Start();  
  8.     // This method doesn't take anytime at all.  
  9.     Program.DoSomething();  
  10.     // Execution ends here  
  11.     Console.WriteLine("Main thread ends here.");  
  12.     Console.ReadKey();  
  13. }  

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 the case of multiple threads, the thread scheduler with help of 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 a dual core processor, 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 in IO processing and some background database operations, you should manage it so thateach 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

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 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. }  

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

Listing 1.

In 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 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 seconds while the secondary thread prints a number every 1.0 seconds and both threads run in parallel.

C# Thread Basics
Figure 1

Name, Priority, and 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 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 current thread

The Thread.CurrentThread returns the current thread that is executing the current code. The following code snippet prints 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

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 are 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 a thread

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();  

Summary

This article is an introduction to threading in .NET. In this article, we learned why multi-threading is useful and how we can create and manage worker threads. In the next article, I will discuss managed thread pool and why it is useful when dealing with multiple threads and resources.