Multithreading in C#

In C#, multithreading is the ability of a central processing unit (CPU), or a single-core or multi-core processor, to execute multiple threads concurrently. This allows the program to perform multiple tasks simultaneously, making the program more efficient and responsive.

Multithreading in CSharp
Multithreading in C#

What is Multithreading in C#?

Multi-threading is a process that contains multiple threads in a single process. Here, each thread performs a different task. For example, we have a class with two different methods. Multithreading lets each method run in a separate thread. So the major advantage of multithreading is that it executes multiple tasks simultaneously.

C# Multithreading Example:

Here is an example of how to create a multithreaded program in C# using the System.Threading namespace:

using System;
using System.Threading;

namespace MultithreadingExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create a new thread
            Thread thread = new Thread(ThreadMethod);

            // Start the thread
            thread.Start();

            // Main thread will loop 10 times
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Main thread: " + i);
                Thread.Sleep(1000);
            }

            // Wait for the thread to finish
            thread.Join();
            Console.ReadKey();
        }

        static void ThreadMethod()
        {
            // Thread will loop 5 times
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Thread 1: " + i);
                Thread.Sleep(1000);
            }
        }
    }
}

In the above code example, we create a new thread using the Thread class and the ThreadMethod method as the thread’s entry point. We then start the thread using the Start method. The main thread then loops 10 times, printing out a message and sleeping for 1 second for each iteration. Meanwhile, the new thread executes the ThreadMethod method, which also loops 5 times and prints out a message. Finally, the main thread waits for the new thread to finish using the Join method.

Once we run the program, the output would be something like this:

Main thread: 0
Thread 1: 0
Main thread: 1
Thread 1: 1
Thread 1: 2
Main thread: 2
Main thread: 3
Thread 1: 3
Thread 1: 4
Main thread: 4
Main thread: 5
Main thread: 6
Main thread: 7
Main thread: 8
Main thread: 9

As you can see, the main thread and the new thread are executing concurrently, with each thread printing out a message every second.

Advantages to using multithreading in C#

There are several advantages to using multithreading in C#:

  1. Improved performance: By utilizing multiple threads, a program can perform multiple tasks concurrently, potentially improving the overall performance of the program.
  2. Better utilization of system resources: By allowing multiple threads to run concurrently, the program can make better use of the available system resources, such as CPU and memory, resulting in more efficient resource utilization.
  3. Enhanced responsiveness: By using separate threads for tasks that may take a long time to complete, such as file I/O or network operations, the program can remain responsive to user input while performing those tasks.
  4. Improved scalability: Multithreaded programs can scale better on systems with multiple CPU cores or processors. This is because each thread can potentially be run on a separate core, leading to improved performance.
  5. Simplified code structure: Using separate threads for different tasks allows you to isolate each task to its own thread, allowing you to structure your program code in a more modular and maintainable manner.

Synchronous and asynchronous programming in C#

In C#, synchronous programming refers to a method of execution in which a program waits for a particular task to complete before moving on to the next task. This means that the program will block, or pause until the task has finished.

In simple words, synchronous means executing one or more tasks one after the other.

On the other hand, asynchronous programming refers to a method of execution in which a program can perform multiple tasks concurrently. In asynchronous programming, the program can start a task and then continue with other tasks while the first task is executed in the background.

Pictorial representation of synchronous processing

threading in C#

Asynchronous means you’re executing multiple tasks at the same time. Here, the second task does not need to wait for the first task to finish; they can execute concurrently.

Microsoft’s Windows operating system is a good example of multitasking because you can run multiple applications at once like Word, PowerPoint, Media players, Web browsers, etc.

A new thread is created in the system for each application that is launched in order to run them all asynchronously.

Pictorial representation of multithreading in C#

C# Multithreading
Multithreading in C#

By default, every process in C# has at least one (Main) thread to run the application.

Synchronous and asynchronous programming example:

Here is an example of synchronous and asynchronous programming in C# using the System.Threading.Tasks namespace:

using System;
using System.Threading.Tasks;

namespace AsyncProgramExample
{
    class Program
    {
        static void SynchronousMethod()
        {
            Console.WriteLine("Start of SynchronousMethod.");
            Task.Delay(TimeSpan.FromSeconds(3)).Wait(); // wait for 3 seconds
            Console.WriteLine("End of SynchronousMethod.");
        }

        static async void AsynchronousMethod()
        {
            Console.WriteLine("Start of AsynchronousMethod.");
            await Task.Delay(TimeSpan.FromSeconds(3)); // wait for 3 seconds
            Console.WriteLine("End of AsynchronousMethod.");
        }
        static void Main(string[] args)
        {
            Console.WriteLine("Synchronous example:");
            SynchronousMethod();
            Console.WriteLine("Asynchronous example:");
            AsynchronousMethod();
            Console.WriteLine("Press any key to exit.");

            Console.ReadKey();
        }      
    }
}

In this example, the SynchronousMethod method blocks the program’s execution for 3 seconds using the Wait method. The AsynchronousMethod method, on the other hand, uses the await keyword to asynchronously wait for 3 seconds before continuing.

When the program is run, the output would be something like this:

Synchronous example:
Start of SynchronousMethod.
End of SynchronousMethod.
Asynchronous example:
Start of AsynchronousMethod.
Press any key to exit.
End of AsynchronousMethod.

As you can see, the synchronous method blocks the program’s execution until the task is complete, while the asynchronous method allows the program to continue execution while the task is being performed. Asynchronous programming is often used to improve the responsiveness and scalability of a program, particularly when it involves tasks that may take a long time to complete, such as file I/O or network operations.

Synchronous programming (Single Threaded) Example

using System;
namespace SynchronousProgramming
{
    class Program
    {
        static void Main(string[] args)
        {
            Task1();
            Task2();
            Task3();
        }
        public static void Task1()
        {
            for(int i=1;i <= 3; i++)
            {
                Console.WriteLine($"Task1: i = {i}");
            }
        }
        public static void Task2()
        {
            for (int i = 1; i <= 3; i++)
            {
                Console.WriteLine($"Task2: i = {i}");
            }
        }
        public static void Task3()
        {
            for (int i = 1; i <= 3; i++)
            {
                Console.WriteLine($"Task3: i = {i}");
            }
        }
    }
}


Synchronous programming output
The output of the above C# program.

In the above result, we can see that all the main threads execute all the tasks one after the other. Here, The next task needs to wait until the first task will get completed.

To overcome this problem, we can use multithreading to execute all the tasks parallelly.

Example of multithreading in C#

using System;
using System.Threading;

namespace Multhreading
{
    class Program
    {
        static void Main(string[] args)
        {
            // Main Thread
            Thread mainThread = Thread.CurrentThread;
                   mainThread.Name = "Main Thread";

            Console.WriteLine($"{mainThread.Name} started");

            // Creating worker threads
            Thread thread1 = new Thread(Task1);
            thread1.Name = "Thread 1";

            Thread thread2 = new Thread(Task2);
            thread2.Name = "Thread 2";

            Thread thread3 = new Thread(Task3);
            thread3.Name = "Thread 3";
           
            // Executing the tasks
            thread1.Start();
            thread2.Start();
            thread3.Start();

            Console.WriteLine($"{mainThread.Name} ended");
            Console.ReadLine();

        }
        public static void Task1()
        {
            Console.WriteLine($"Task1 started using {Thread.CurrentThread.Name}");
            for(int i=1;i <= 3; i++)
            {
                Console.WriteLine($"Task1: {i} {Thread.CurrentThread.Name}");
                Thread.Sleep(TimeSpan.FromSeconds(2));
            }
            Console.WriteLine($"Task1 completed using {Thread.CurrentThread.Name}");
        }
        public static void Task2()
        {
            Console.WriteLine($"Task2 started using {Thread.CurrentThread.Name}");
            for (int i = 1; i <= 3; i++)
            {
                Console.WriteLine($"Task2: {i} {Thread.CurrentThread.Name}");
                Thread.Sleep(TimeSpan.FromSeconds(2));
            }
            Console.WriteLine($"Task2 completed by {Thread.CurrentThread.Name}");
        }
        public static void Task3()
        {
            Console.WriteLine($"Task3 started by {Thread.CurrentThread.Name}");
            for (int i = 1; i <= 3; i++)
            {
                Console.WriteLine($"Task3: {i} {Thread.CurrentThread.Name}");
                Thread.Sleep(TimeSpan.FromSeconds(2));
            }
            Console.WriteLine($"Task3 completed by {Thread.CurrentThread.Name}");
        }
    }
}

Multithreading program result.
The output of the above C# multithreading program.

Here, the output of the above program may vary on different systems.

As we can see in the above output, all the tasks are executing asynchronously.

Multithreading allows for the maximum utilization of the CPU by running multiple programs concurrently.

Note: In single-processor architecture, the CPU switches between threads so quickly that it gives the illusion of parallelism even if only one thread is actually active at a given time.

What is Thread?

In C# programming, a thread is a separate flow of execution within a program. A program can have multiple threads, which can run concurrently and perform different tasks simultaneously. This can enable a program to perform multiple tasks at the same time, increasing the program’s efficiency and responsiveness.

In C#, threads are implemented using the System.Threading.Thread class. To create a new thread, you can use the Thread class’s constructor and pass it the entry point method for the thread. You can then start the thread using the Start method.

In C#, A Thread is a basic unit to which an operating system allocates CPU time to execute the logic of the program.
By default, every application or program in .NET is started with a single thread called the main thread or the primary thread.
The main thread is used to create additional threads (Worker thread) when required to run multiple tasks concurrently.

Thread Life Cycle

A thread’s life cycle begins with the creation of a Thread class object and ends when the thread is terminated or completes execution.
The various stages of a thread’s life cycle are listed here.

Sr.No.Thread stateDescription
1.Unstarted state:This is the state when the Thread class instance has been created, but the Start() method has not yet been called.
2.Runnable State:When the thread’s start() method is invoked, the thread is in a runnable state, or ready to run, and is waiting for a CPU cycle to complete.
3.Running State:At the time of execution, the thread is in the running state. This is the state where the thread gets the processor.
4.Not Runnable State:A thread is not executable because:
1.) Sleep() method has been called.
2.) Wait() method has been called.
3.) Blocked Due to I/O request.
4.) Suspend() method has been called.
Multithreading- Thread Life Cycle

Types of Threads in C#

In C#, there are two types of threads,  foreground  and   background   thread.

As you know, a program needs at least one thread to run and that is called the main thread. When you explicitly create threads that execute multiple methods, they become child threads or worker threads of the main thread.

Foreground thread in C#

Foreground Thread: Foreground threads keep running even if their main thread quits or completes its task. It is also known as the worker thread and its lifespan doesn’t depend on the main thread.

  • A foreground thread prevents an application from being terminated until it’s done with the execution of the task.
  • By default, a thread is created as a foreground thread.

Example of foreground threads in C#

using System;
using System.Threading;

namespace ForegroundThreadDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Main thread started");

            // Creating foreground thread
            Thread workerThread = new Thread(Task1);
            workerThread.Name = "Worker Thread";
  
            // Executing the tasks
            workerThread.Start();

            Console.WriteLine("Main thread ended");
            Console.ReadLine();

        }
        public static void Task1()
        {
            Console.WriteLine($"Task1 started using {Thread.CurrentThread.Name}");
            for(int i=1;i <= 5; i++)
            {
                Console.WriteLine($"i = {i} {Thread.CurrentThread.Name}");
                //Pausing the thread execution for 2 seconds
                Thread.Sleep(TimeSpan.FromSeconds(2));
            }
            Console.WriteLine($"Task1 completed using {Thread.CurrentThread.Name}");
        }
        
    }
}

Foreground Thread
The output of the above C# program.

In the above result, the foreground thread or worker thread continued execution and completed its task even after the main thread was ended.

So, the lifespan of the foreground thread doesn’t depend upon the main thread or primary thread. It can live without the main thread.

Background thread in C#

Background thread: By default, every thread we created explicitly in C# is a foreground thread.
To make a thread as a background thread, we have to set the  IsBackground  property of that particular thread to true.

The lifespan of the background thread depends upon the main thread. If the main thread quit then the background thread will also get terminated immediately.

In C#, The CLR is used to end the process once all the foreground thread belonging to a process has terminated.
After then all the other remaining background threads will be stopped immediately and prevent further execution of the assigned tasks.

Example of background threads in c#.

using System;
using System.Threading;

namespace BackgroundThreadDemo
{
    class Program
    {
        static void Main(string[] args)
        {          
         // Creating background thread
         Thread bgThread = new Thread(Task1);

         bgThread.IsBackground = true;

         bgThread.Name = "Background Thread";

        // Executing the tasks
         bgThread.Start();

        Console.WriteLine("Main thread quit");
      
        }
        public static void Task1()
        {          
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine($"i = {i} {Thread.CurrentThread.Name}");
                //Pausing the thread execution for 2 seconds
                Thread.Sleep(TimeSpan.FromSeconds(2));
            }
            Console.WriteLine("Backgroung thread quit");
        }

    }
}

Background thread output
The output of the above C# program.

In the above result, we can see that once the main thread quits, the background thread also gets terminated immediately.

How to Use Multi-Threading With Tasks in C#?

According to MSDN, the Task Parallel Library (TPL) was introduced in the .NET Framework 4 and is a set of common APIs and types in the System.Threading and System.Threading.Tasks namespaces. The purpose of TPL is to improve developer productivity by simplifying the process of adding parallelism and concurrency to the applications.
In C #, TPL is the preferred way to write multithreaded and parallel programming.

The main purpose of the Task class in TPL is to represent asynchronous operations. The most common way to get started is to use Task.Factory.StartNew method as shown in the following example:

using System;
using System.Threading;
using System.Threading.Tasks;
namespace TaskInMultithreading
{
    class CustomData
    {
        public string CreationTime;
        public int taskName;
        public int ThreadId;
    }

    public class Example
    {
        public static void Main()
        {
            // Creating the task object by using an Action(Of Object) to pass in custom data
            // to the Task constructor. This is useful when you need to capture outer variables
            // from within a for or foreach loop.

            Console.WriteLine("** Example - Multi Threading With Tasks in C# **");
            Task[] taskArray = new Task[10];
            for (int i = 0; i < taskArray.Length; i++)
            {
                taskArray[i] = Task.Factory.StartNew((Object obj) => {
                    CustomData data = obj as CustomData;
                    if (data == null)
                        return;
                    data.ThreadId = Thread.CurrentThread.ManagedThreadId;
                    Console.WriteLine($"Task # {data.taskName} created at {data.CreationTime} on thread #{data.ThreadId}.");

                },
                     new CustomData() { taskName = i, CreationTime = DateTime.Now.ToString("HH:mm:ss:ffff") }
                );
            }
            Task.WaitAll(taskArray);
            Console.ReadKey();
        }
    }
}
Multi Threading With Tasks in C#
Multi-Threading With Tasks in C#

Why Task? Why not Thread?

Here are the reasons for using tasks instead of threads in multithreaded application:

    • Use of system resources more efficiently and extensively: Tasks are queued to the ThreadPool behind the scenes, which has been updated with algorithms that determine and adjust the number of threads, as well as load balancing to improve performance. This makes the tasks relatively lightweight compared to threads. If we start a thread directly, it will create a new thread. It will not run in the ThreadPool.
    • Thread creation can result in huge costs. It also brings the overhead of context switching within the application. Therefore, it can cause poor performance problems in a single-core environment.
    • More programmatic control than threads or work items: TPL supports waiting, cancellation, continuations, robust exception handling, and so on.

For writing multi-threaded, asynchronous, and parallel programming in .NET, TPL is the preferred API.

To learn more about TPL, please visit this MSDN: Task Library, threads, and threading

FAQ

Q: What is the difference between a thread and a process in C#?

Ans: A process is what an operating system uses to run a program by providing the necessary resources. A unique process ID is assigned to each process. You can use Windows Task Manager to view the processes in which the program is running.
The states of the process are as follows: new, running, ready, waiting, terminated, and suspended.

A thread is a segment of a process, which means that a single process can have many threads, all of which are contained within the process. A thread can be in one of three states: running, ready, or blocked.

Q: What is Semaphore in multithreading?

Ans: Semaphore is a non-negative int32 variable that is shared between threads to limit the number of threads that can enter a critical region in a multithreaded environment.
A semaphore is a signaling mechanism because if the semaphore is 0, threads waiting for the semaphore will block and if the semaphore is greater than 0, it will unlock.
Semaphore is useful to prevent race conditions in the multitasking operating system.

Q: How can we create a Thread in C#?

Ans: A Thread can be created by using a Thread class available in System.Threading namespace.
First, create a new ThreadStart delegate. Include this delegate as an argument when creating a new Thread instance, and it will point to a method. Finally, to run your function (in our example, MyThreadFunction) in the background, use the Thread.Start method.
Example:
Thread thread = new Thread(new ThreadStart(MyThreadFunction));
thread.Start();

Q: What is the significance of thread join and thread isAlive methods in multithreading?

Ans: isAlive() returns a Boolean value that indicates whether the thread is alive. The thread status returns true or false, which indicates whether the thread is completed.
join() method makes the current thread wait for the thread calling join() method to complete.

Q: Under what circumstances should you use multithreading?

Ans: The process of running multiple threads concurrently is known as multithreading. Multithreading should be used when multiple operations can be performed together to save time.

Conclusion:

Tasks are queued to the ThreadPool behind the scenes, which has been updated with algorithms that determine and adjust the number of threads, as well as load balancing to improve performance. This makes the tasks relatively lightweight compared to threads.
If we start a thread directly, it will create a new thread. It will not run in the ThreadPool.

I hope you found this post useful in learning about “multithreading in C#“. Your comments, suggestions, and constructive criticism are much appreciated.

Recommended Articles:

Shekh Ali
0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments