Mastering C#: Unraveling the Power of C# & OOPS

Prasoon AbhinawPrasoon Abhinaw
10 min read

Introduction

You might have already come across this multithreading word or multithreading concept in C# .NET, or in any other programming language.

Here, in this article, I’ll try to explain the concept in an easy way. I’ll try focusing on what is multithreading, why we use it and in what type of situations it is most useful. Also, is it different from asynchronous or not, and the structure of multithreading along with some basic examples in order to understand.

Execution of the program or a code segment is always been executed in two ways.

Synchronous way: In a synchronous way, multiple code segments are executed one after the other.

Asynchronous way: In an asynchronous way, multiple code segments are executed in a parallel way. Doesn’t need to wait till the execution of the one code segment to start the execution of another code segment.

Before getting into details, we define, what is a thread and multithreading metaphorically in the real world.

Code example for use of Async- Await

using System;
using System.Threading.Tasks;

namespace AsyncAwaitExample
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            // Asynchronously wait for the task to complete
            await AsyncMethod();

            Console.WriteLine("Async method has completed.");
        }

        static async Task AsyncMethod()
        {
            // Asynchronously wait for 2000 milliseconds
            await Task.Delay(2000);
            Console.WriteLine("Async method completed delay.");
        }
    }
}


//OUTPUT

Hello World!
Async method completed delay.
Async method has completed.

What is Process? The operating system uses ‘Process’ to facilitate the execution of a program by proving the resource required. Each process has a unique process id with it. By using the windows task manager you can view the processes in which a program is being executed.

What is Thread? A Word Editor saving the article automatically from time to time is known as Thread. (This means that it's one way of execution).

What is Multi Thread? While Word Editor is saving the article, Grammarly is working behind to find the mistakes in the sentences is known as Multithread. (Having more than one execution.)

What is Multithreading? Multitasking is the simultaneous execution of multiple tasks or processes over a certain time interval. Windows operating system is an example of multitasking because it is capable of running more than one process at a time like running Google Chrome, Notepad, VLC player, etc. at the same time. The operating system uses a term known as a process to execute all these applications at the same time. A process is a part of an operating system that is responsible for executing an application. Every program that executes on your system is a process and to run the code inside the application a process uses a term known as a thread.
A thread is a lightweight process, or in other words, a thread is a unit which executes the code under the program. So every program has logic and a thread is responsible for executing this logic. Every program by default carries one thread to executes the logic of the program and the thread is known as the Main Thread, so every program or application is by default single-threaded model. This single-threaded model has a drawback. The single thread runs all the process present in the program in synchronizing manner, means one after another. So, the second process waits until the first process completes its execution, it consumes more time in processing.

What is Parallel Programming? It is the flow of program where the application is broken into parts and for each part, there will be CPU's which deals at the same time. So, by dividing these parts the overall time taken by an application will be reduced that’s how an application performance will be increased.

(This means that it's saying Hey! My name is parallel, I simply distribute my work so that there won't be a burden on me, and all my friends who have taken my work will return it to me at the same time after finishing it and I’ll wait until they do so)

Are Parallel Programming and Asynchronous the same?

Asynchronous is a nonblocking way of doing things, it executes without waiting for an event, and carry on with things.

(This means It's saying Hey! I'm asynchronous, please go and do this task for me and come back with results after you have finished, and meanwhile, I’ll do some other work). You can find more about asynchronous here.

The difference is, Parallel mostly depends on Hardware whereas synchronization is about Tasks, and Threading is about workers.

Note: C# supports parallel execution of code with the help of multithreading. (means multithreading is about the parallel way of execution)

Structure of Multithreading

We use the “Thread” Keyword which is a class taken from the System.Threading namespace.

How a Thread is created? To implement a thread, we need to make use of threading namespace in our program and this namespace has Thread class, we use this to create a Thread object and these threads objects create threads in the application.

How Threading Works? In multithreading, the thread scheduler takes help from the operating system to schedule threads so that it can allocate time for each thread.

In a single Processor machine, only one thread executes at a time and for dual-core processor ideally, we can have about 4 threads and for a quad-core processor, we can create up to 8 threads. Improper management also creates more threads on a single processor machine that may lead to resource bottleneck.

Types of Threads in C#

Foreground Thread

A thread which keeps on running to complete its work even if the Main thread leaves its process, this type of thread is known as foreground thread. Foreground thread does not care whether the main thread is alive or not, it completes only when it finishes its assigned work. Or in other words, the life of the foreground thread does not depend upon the main thread.

Background Thread

A thread which leaves its process when the Main method leaves its process, these types of the thread are known as the background threads. Or in other words, the life of the background thread depends upon the life of the main thread. If the main thread finishes its process, then background thread also ends its process.
Note: If you want to use a background thread in your program, then set the value of IsBackground property of the thread to true.

Example Program on Multithreading

using System;  
using System.Threading;  
class Program {  
    public static void Main() {  
        Thread ThreadObject1 = new Thread(Example1); //Creating the Thread    
        Thread ThreadObject2 = new Thread(Example2);  
        ThreadObject1.Start(); //Starting the Thread    
        ThreadObject2.Start();  
    }  
    static void Example1() {  
        Console.WriteLine("Thread1 Started");  
        for (int i = 0; i <= 5; i++) {  
            Console.WriteLine("Thread1 Executing");  
            Thread.Sleep(5000); //Sleep is used to pause a thread and 5000 is MilliSeconds that means 5 Seconds    
        }  
    }  
    static void Example2() {  
        Console.WriteLine("Thread2 Started");  
        for (int i = 0; i <= 5; i++) {  
            Console.WriteLine("Thread2 Executing");  
            Thread.Sleep(5000);  
        }  
    }  
}

What is MainThread? A Thread that executes first in a process is known as the MainThread.

What does Process mean in Multithreading? To execute a program, we need some resources provided by this Process. Each process starts with a single thread, called a primary thread. This Process also has the ability to create additional threads from the thread it possesses.

What is Synchronization? Synchronization is a concurrent execution of two or more threads.

Why this synchronization is needed? Synchronization avoids conflicts between threads, these conflicts arise when they are running in a parallel way trying to modify some variables. Synchronization makes sure that only one process or thread accesses the critical section of the program.

How do we manage Synchronization? To handle this, we have various methods, divided into 4 categories

  • Blocking Methods

  • Locking Constructs

  • No blocking Synchronization

  • Signaling

Here, we try to demonstrate a few methods for understanding:

Join

In the thread, synchronization join is a blocking mechanism that helps in pausing the calling thread. This continues until the thread execution has finished on which the join is called.

Here we try to find the differences with Join() and without Join():

using System;  
using System.Threading;  
class Program {  
    public static void Main() {  
        //Creating the WorkerThread with the help of Thread class.    
        Thread ThreadObject1 = new Thread(WorkerThread);  
        ThreadObject1.Start(); //Starting the Thread    
        //ThreadObject1.Join(); //Using Join to block the current Thread    
        Console.WriteLine("1. MainThread Started");  
        for (int i = 0; i <= 3; i++) {  
            Console.WriteLine("-> MainThread Executing");  
            Thread.Sleep(3000); //Here 5000 is 5000 Milli Seconds means 5 Seconds    
        }  
        // We are calling the Name of Current running Thread using CurrentThread    
        Thread Th = Thread.CurrentThread;  
        Th.Name = "Main Thread";  
        Console.WriteLine("\nGetting the Name of Currently running Thread");  
        //Name Property is used to get the name of the current Thread    
        Console.WriteLine($ "Current Thread Name is: {Th.Name}");  
        //Priority Property is used to display the Priority of current Thread    
        Console.WriteLine($ "Current Thread Priority is: {Th.Priority}");  
    }

Prevent concurrent access of shared resources in multithreading:

By using the Lock keyword ensures that one thread is executing a piece of code at one time. The lock keyword ensures that one thread does not enter a critical section of code while another thread is in that critical section.

Let’s take an example. Within the main method, we create 3 threads and execute the CalculateResuls() function. Every time we run this, we get different outputs because the total field is a shared resource and it is not protected from the concurrent access from the multiple threads.

Code for using lock

using System.Threading;
using System;
namespace ThreadingDemo
{
    class Program
    {
        static int Count = 0;
        static void Main(string[] args)
        {
            Thread t1 = new Thread(IncrementCount);
            Thread t2 = new Thread(IncrementCount);
            Thread t3 = new Thread(IncrementCount);
            t1.Start();
            t2.Start();
            t3.Start();
            //Wait for all three threads to complete their execution
            t1.Join();
            t2.Join();
            t3.Join();
            Console.WriteLine(Count);
            Console.Read();
        }
        private static readonly object LockCount = new object();
        static void IncrementCount()
        {
            for (int i = 1; i <= 1000000; i++)
            {
                //Only protecting the shared Count variable
                lock (LockCount)
                {
                    Count++;
                }
            }
        }
    }
}

Best practices for debugging and testing multithreaded code:

  1. Use the Thread.Join method to wait for a thread to finish execution: The Thread.Join method allows you to wait for a thread to complete its execution before continuing with the rest of your program. This can be useful when you want to ensure that a particular thread has finished its work before proceeding.

  2. Use the Thread.IsAlive property to check the status of a thread: The Thread.IsAlive property returns a boolean value indicating whether a thread is currently running or not. This can be useful for checking the status of a thread, or for waiting for a thread to complete its execution.

  3. Use the Thread.Abort method with caution, as it can cause unpredictable behavior: The Thread.Abort method allows you to terminate a thread abruptly, which can be useful in certain situations. However, it's important to use this method with caution, as it can cause unpredictable behavior, including the possibility of data corruption or resource leaks.

  4. Utilize the Concurrency Visualizer in Visual Studio to visualize and analyze the behavior of your multithreaded code: The Concurrency Visualizer is a powerful tool available in Visual Studio that allows you to visualize and analyze the behavior of your multithreaded code. You can use it to see how your code is utilizing CPU resources, identify potential performance bottlenecks, and optimize your code for better-multithreaded performance.

Real-world examples of using C# multithreading

  1. Multithreaded data processing: One common use case for C# multithreading is to perform resource-intensive data processing tasks concurrently. For example, imagine that you have a large dataset that you need to analyze and process. By dividing the dataset into smaller chunks and processing them concurrently on different threads, you can greatly improve the performance of your program.

  2. Multithreaded UI updates: In a graphical user interface (GUI) application, it’s important to ensure that the UI remains responsive to user input even when the program is performing resource-intensive tasks. By using multithreading, you can perform these tasks concurrently in the background, while still allowing the UI to remain responsive.

  3. Multithreaded server-client communication: In a client-server application, it’s often useful to use multithreading to allow multiple clients to communicate with the server concurrently. This can help improve the performance and scalability of the server, and reduce the latency experienced by clients.

Conclusion

In this blog post, I have explored the various ways in which C# can be used to implement multithreading, and I have provided tips and best practices for maximizing the performance and reliability of multithreaded C# code. Some of the key points that I covered include:

  • The benefits of using the Task class and async/await keywords for asynchronous programming

  • The trade-offs between parallelism and concurrency, and how to choose the right approach for your needs

  • The importance of using thread-safe collections and the lock keyword to synchronize access to shared resources

  • Techniques for debugging and testing multithreaded C# code, such as the Thread.Join method and the Concurrency Visualizer in Visual Studio.

Thanks for your time. I hope this post was helpful and gave you a mental model for Thread and Multithreading!

0
Subscribe to my newsletter

Read articles from Prasoon Abhinaw directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Prasoon Abhinaw
Prasoon Abhinaw

I am a passionate software engineer with over 16 years of hands-on experience in the tech industry. On this blog, I aim to share my expertise, insights, and love for engineering with all of you. Whether you're a seasoned developer or just starting your journey in the world of code, this blog is here to empower you and elevate your skills. What to Expect: Expect a diverse range of content, including in-depth tutorials, coding challenges, software development tips, algorithmic insights, tech reviews, and discussions on the latest trends in the tech sphere. We'll explore various programming languages, design patterns, data structures, and best practices to help you become a more proficient engineer.