Multithreading in C#

What is Multithreading?

Multithreading in C# is a process to execute multiple threads simultaneously to run multiple programs or tasks at a time.

In C#, multithreading helps us to run multiple tasks parallelly or asynchronously.

In order to create a multithreading application, we need to import System.Threading namespace.

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

C# Multithreading

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 executing 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 that all the tasks are executing asynchronously.

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

The main advantage of multithreading is to make a user interface responsive.

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.

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

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

    1. 10 Difference between interface and abstract class In C#
    2. Exception Handling in C#| Use of try, catch and finally block
    3. C# Enum | How To Play With Enum in C#?
    4. C# extension methods with examples
    5. Properties In C# with examples
    6. Generic Delegates in C# With Examples
    7. Constructors in C# with Examples
    8. C# Dictionary with Examples
    9. IEnumerable Interface in C# with examples
    10. Access Modifiers in C#

Leave a Reply