Multithreading in C#

Multithreading in CSharp
Multithreading in C#

What is Multithreading in C#?

Multithreading in C# is a process to execute multiple threads simultaneously to run multiple programs or tasks at a time.
It is a process to achieve multitasking that allows you to run multiple jobs in parallel or asynchronously.
The main advantage of multithreading in C# is that it can make the user interface more responsive.
We need to import  System.Threading  namespace in order to create a multithreading application

Let’s understand the concept of Synchronous and Asynchronous programming in C#.

Synchronous: In Synchronous programming, all the tasks run in a sequence. Here, the next task has to wait until the first task gets completed.

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

Pictorial representation of synchronous processing

threading in C#

Asynchronous: Asynchronous means executing multiple tasks simultaneously. Here, the second task doesn’t need to wait for the first task to get completed, they can run simultaneously.

Windows operating system is the best example of multitasking where we can run multiple applications like MS Word, Powerpoint, Media players, Web browser, etc. at the same time.

For every application to be launched, a new thread is generated in the system to run all these applications 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.

Example of synchronous programming (Single Threaded)

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.

What is Thread?

In C#, A  thread  is defined as the basic unit to which an operating system allocates CPU time to execute the logic of the program.
By default, every application or a 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.

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 when 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 we already know that to run a program we need at least one thread called the main thread.
If we create threads explicitly to run multiple methods those threads will become the child thread or worker thread for the main thread.

Foreground thread in C#

Foreground Thread: Foreground threads are the threads that keep on running to finish its task even the main thread quits or finished its task.

It is also known as 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 the main thread was ended.

So, the lifespan of the foreground thread doesn’t depend upon the main thread or primary thread. It can alive 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.

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

Leave a Comment