Complete Threading Tutorial (C#)

Discussion in 'C#' started by shabbir, Apr 15, 2014.

  1. shabbir

    shabbir Administrator Staff Member

    Joined:
    Jul 12, 2004
    Messages:
    15,375
    Likes Received:
    388
    Trophy Points:
    83
    Often times, applications need to perform multiple tasks at a time. I always share Microsoft’s word processor’s example. While you type in the word processor, the application lets you type and in the meantime it is running spell-check and dictionary in the background to inform you whenever you type wrong spelling of a word. Basically multiple tasks are being done in a single program. This is the principle concurrency, to take advantage of multiprocessor hardware and to run multiple application parts simultaneously. Concurrency results in a more responsive application. Concurrency in .NET is implemented via threads. For now, you just need to keep in mind that tasks that take too much time should be run in a separate thread so that application can perform other important tasks meanwhile. Let us start with the most fundamental concept of concurrency i.e Threading.
    Simplest definition of a thread is that “A path of execution which can be run independent of other parts of application”. You need to recall your operating system concepts here. A thread is executed inside an operating system process. Processes is an in memory representation of a program and it provides isolated environment in which a program basically runs. In case of a single-threaded application, one thread has excess to all of the resources of the process and the process isolated environment is dedicated to that thread. However, in case of multi-threaded program, the corresponding process’s isolated environment is shared by multiple threads (Memory and Resources in particular). This is where multithreading is beneficial. For instance, since memory is being shared, we can assign one thread, responsibility of fetching data from the data source and the other thread can display the data. In this way, efficiency of application can be improved multiple times.

    How to create a thread?



    As usual, .NET Framework comes with a set of classes that help us create thread right away. These classes are located in System.Threading namespace. In case of a client application such as a console application or a windows forms application, a static thread is automatically created by operating system which is responsible for kick-starting the application. This thread is the “Main” thread which starts when the Main method starts execution. Inside this Main method, you can start as many threads as you want. In our first example we are going to demonstrate this concept. To see how threads are actually created, have a look at our first example.

    Example1
    Code:
    using System;
    using System.Threading;
    
    namespace CSharpTutorial
    {
        class Program
        {
            public static void DisplayZero()
            {
                for (int i = 0; i < 1000; i++)
                {
                    Console.Write(0);
                }
            }
    
            public static void Main()
            {
                Thread zero = new Thread(DisplayZero);
                zero.Start();
    
                for (int i = 0; i < 1000; i++)
                {
                    Console.Write(1);
                }
    
                Console.ReadLine();
            }
        }
    }
    
    Here we have method which we have named DisplayZero, this method displays zero thousand times. Inside the Main method we create a thread by instantiating Thread class and then by calling its Start method. We did this in these lines:
    Code:
    Thread zero = new Thread(DisplayZero);
    zero.Start();
    
    We have to pass the method that we want to run in a separate thread to the constructor of the thread class. Note, you must not pass the parameters with it. You can see we just passed the name “DisplayZero” to the Thread constructor. Next, in order to start the thread’s execution, just call Start method on the thread’s object.

    When you call Start, two things would happen. A new thread starts execution and the control shifts to the next line in the Main thread. Which means that now, DisplayZero method is being executed in parallel with the Main method. In the Main method, we displayed one, thousand times. Now, DisplayZero thread is printing zeros and Main thread is printing ones, simultaneously. Both of these threads are sharing the console which would shift between two threads in an irregular manner. When you look at the output you would see irregular patterns of ones and zeros. The output of the code in Example1 is as follows:

    Output1

    [​IMG]

    When a thread is running, its IsAlive property returns true. A thread stops when the execution of the delegate passed to its constructor ends. When a thread has terminated, it cannot be restarted ever again.

    Join & Sleep Thread



    Join and Sleep are two of the extremely important methods of the Thread class. When you call join on a thread object. The thread from which join is called waits for the thread to complete on which join is called. Sleep method pauses the thread in which it is called for a time period specified in its parameters. Our next example explains the concept of Join and Sleep methods. Have a look at Example2 of this article.

    Example2
    Code:
    using System;
    using System.Threading;
    
    namespace CSharpTutorial
    {
        class Program
        {
            public static void DisplayZero()
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.Write(0);
                    Thread.Sleep(TimeSpan.FromSeconds(1)); 
                }
            }
            public static void Main()
            {
                Thread zero = new Thread(DisplayZero);
                zero.Start();
                zero.Join();
    
                Console.WriteLine();
                for (int i = 0; i < 10; i++)
                {
                    Console.Write(1);
                }
                Console.ReadLine();
            }
        }
    }
    
    The code in Example2 is similar to what we had in Example1 but here in this case zero would be printed ten times in Display zero methods and one would be printed ten times in Main method. Notice another thing in the Main method after we started the zero thread. We called zero.Join(), this method would tell the Main method that you should wait until the zero thread completes its execution.

    Now, come towards the DisplayZero method that would run in zero thread created in the Main method. Inside the DisplayZero method, we called static Sleep method of the Thread class and pass it a time span of one second. Now if you build and run the above code, you would see that first all the zeros would be displayed after a delay of one second and then ones would be printed. The delay is because of sleep and printing of all zeros is because of Join method which would block the execution of the for loop that displays ones in the Main thread, until zero thread completes. The output of the code in Example2 is as follows:

    Output2

    [​IMG]

    You would see that zeros are printed after one second because there is a Sleep of one second in zero’s thread while ones would be printed immediately since there is no Sleep in the Main method that prints one.

    An important point to note here is that when a thread goes to blocking state via a Join or Sleep, it relinquishes its CPU slice of time and CPU can process other threads mean while until the sleep time is over or a thread is again invoked at the end of Join. CPU performs context switching when a thread goes to blocking or unblocking state.

    IO Bound Operations vs. CPU Bound Operations



    In operating system’s terms, operations can be of two types. IO bound operations are those who wait for something to open. These operations are implemented via threads that wait for something to happen such as downloading of a webpage, waiting for the user to enter something etc. CPU bound operations are those operations who spend most of their time performing CPU intensive tasks.

    Blocking vs. Spinning

    Another important concept is that of Spinning. Blocking occurs when a thread waits for something to happen. Spinning occurs when a thread executes continuously in the form of a loop waiting continuously for something to happen.

    Local vs. Shared Thread data

    If several threads are sharing a process’s isolated environment, they have some shared data and some common data. Local variables are exclusive to a thread and stored on individual thread’s local variable stack. They are not shared by all the threads in the process. Our next example demonstrates this concept.

    Example3
    Code:
    using System;
    using System.Threading;
    
    namespace CSharpTutorial
    {
        class Program
        {
            public static void DisplayNumber()
            {
                int number = 10;
                for (int i = 0; i < number; i++)
                {
                    Console.Write(i);
                }
            }
            public static void Main()
            {
                new Thread(DisplayNumber).Start();
                DisplayNumber();
                Console.ReadLine();
            }
        }
    }
    
    Here, in Example3, we have a method called Display number which displays numbers from 0-9. Inside the main method we created a new anonymous thread and started DisplayNumber method in this thread. Next, we directly called DisplayNumber method from the Main method. Now the Main thread and the new anonymous thread, both are executing the same DisplayNumber method simultaneously. If you look at the out the output you would see that a total of 20 numbers would be printed, ten numbers 0-9 by one thread and ten numbers 0-9 by the other thread. It means that the number variable inside the DisplayNumber thread is local to both the threads that execute this method. It is due to this reason that DisplayNumber method would run ten times for each of the two threads. This is the concept of local thread data; here number variable is local to every thread that calls the DisplayNumber method. The output of the code in Example3 is as follows:

    Output3

    [​IMG]

    You can see that the numbers are printed in random order because both of the threads are running in parallel.

    The real strength of the threads lay in their ability to share data among themselves. Data can be shared amongst threads in two ways. If two more threads have common reference to the instance of an object, the data of the object is shared amongst the threads. The other way of sharing data between multiple threads is via static fields. We will demonstrate the second method in our next example. Have a look at the 4th example of this program.

    Example4
    Code:
    using System;
    using System.Threading;
    
    namespace CSharpTutorial
    {
        class Program
        {
            static bool completed = false;
            public static void DisplayNum()
            {
                if (!completed)
                {
                    completed = true;
                    Console.WriteLine(completed);
                }
            }
            public static void Main()
            {
                new Thread(DisplayNum).Start();
                DisplayNum();
                Console.ReadLine();
            }
        }
    }
    
    Have a look at the code in Example4. At the star of our class Program, we defined a static bool variable which we named ‘completed’ and we initialized it to false. We have a method DisplayNum. Inside this method we check that if the bool variable ‘completed’ is false, change its value to true and print this value.

    Like, in the previous method, inside the Main function we created an anonymous thread and passed it the DisplayNum method as a delegate. We start the thread in the same line. Similarly, we directly called the DisplayNum method from the Main method as well. Now, we have two methods simultaneously calling DisplayNum method.

    The real magic begins here, the thread that reaches the condition ‘if (!completed)’ first, will find that bool variable ‘completed’ is false hence it will make this variable true and will display the value. Now, since this ‘completed’ variable is static it is being shared between the Main thread and the new anonymous thread. The thread that reaches the condition ‘if (!completed)’ later would find that ‘completed’ variable is true, therefore it would not execute further. If you see the output of the code in Example4, you would see only single bool value.

    Output4

    [​IMG]

    There is a potential problem with data sharing between threads. In Example4, we do not know whether the Main thread or the new anonymous thread that is responsible for printing the output. It depends upon the thread which got CPU cycles earlier and we do not know the internal mechanism by which CPU cycles are assigned to the tread. So, we do not know that which thread actually executed first.

    Locking and Thread Safety



    If you look at the code in Example4 closely, you would find that both the threads can display the value of the bool variable ‘completed’. For instance if both the threads have checked the if condition but none of them have changed the value of the bool variable ‘completed’ to true, both threads would change it to true and would display its value. (This is rare but possible). We can say that the variable ‘completed’ is not thread safe and some locking mechanism has to be introduced between them. This is what we do in this section.

    .NET framework provides lock statement to which gives exclusive lock to a thread which reaches that statement first and is authorized to execute that statement. Think of lock as a key, when a user wants to enter a room it obtains a key from the administration and while user is in the room no one can enter it. When user leaves the room it gives back the key to the administration and next person in the queue can obtain the key and enter a room. To implement a lock, you need a reference type object followed by a block of code on which this lock is implemented. Our next example modifies the code in Example4. Have a look at our 5th example to understand locks.

    Example5
    Code:
    using System;
    using System.Threading;
    
    namespace CSharpTutorial
    {
        class Program
        {
            static bool completed = false;
            static readonly object _lockobject = new object();
    
            public static void DisplayNum()
            {
                lock (_lockobject)
                {
                    if (!completed)
                    {
                        completed = true;
                        Console.WriteLine(completed);
                    }
                }
            }
            public static void Main()
            {
                new Thread(DisplayNum).Start();
                DisplayNum();
                Console.ReadLine();
            }
        }
    }
    You can see that inside the DisplayNum method, we have following statement:
    Code:
    lock (_lockobject)
    {
        if (!completed)
        {
            completed = true;
            Console.WriteLine(completed);
        }
    }
    
    This statement would make it sure that once a thread has obtained the lock, no other thread can enter this piece of code until the thread which has obtained the lock earlier, yields this lock. The code which prevents multiple threads accessing and modifying data simultaneously is called thread-safe code. We can say that the code in Example5 would is thread-safe. The output of the code in example 5 would be same to that of Example4.

    Output5

    [​IMG]

    Passing Data to Thread



    In all of our previous examples, a new thread is being used to execute a method that doesn’t take any parameters. However, this is not the case with real life programming scenarios where you need to pass data to threads or in other words you want to execute methods with parameters inside threads. But till now, we have been passing delegates with no parameters in the Thread class’s constructor. To pass, data to a thread or to pass parameters to a method that runs in a separate thread, we have two options. We can use lambda expression to initialize a method with parameters inside the constructor of Thread class. Secondly, we can pass parameters to the Start method of the Thread class instance. In our next example, we shall see the lambda expression technique for passing data to threads.

    Example6
    Code:
    using System;
    using System.Threading;
    
    namespace CSharpTutorial
    {
        class Program
        {
            public static void Addition(int num, int num2)
            {
                Console.WriteLine(num + num2);
            }
            public static void Main()
            {
                Thread nt = new Thread(() => Addition(5,10));
                nt.Start();
                Console.ReadLine();
            }
        }
    }
    
    In our Example6, we have a method which we named Addition and this method takes two parameters of type integers. The method then prints the sum of these two numbers on the console. Inside the Main method, we have instantiated the Thread class but in the constructor of the Thread class we passed a lambda expression which calls Addition method with two parameters. This is done in the following line of code:
    Code:
    Thread nt = new Thread(() => Addition(5,10));
    
    After that you simply have to call the Start method on the Thread instance. If you execute the above example, you would see the sum of two passed numbers. The output of the code in Example6 is as follows:

    Output6

    [​IMG]

    Lambda expressions were introduced in .NET framework 3.0, so for the legacy system you might need to adapt the old technique of passing data. Our next example demonstrates this technique. Have a look at the 7th example of this tutorial.

    Example7
    Code:
    using System;
    using System.Threading;
    
    namespace CSharpTutorial
    {
        class Program
        {
            public static void DisplayNumber(object num)
            {
                int number = Convert.ToInt32(num); // Casting
              
                for (int i = 0; i < number; i++)
                {
                    Console.Write(i);
                }
            }
            public static void Main()
            {
            
                Thread nt = new Thread(DisplayNumber);
                nt.Start(7);
                Console.ReadLine();
            }
        }
    }
    
    Note that in the 7th example, the parameter of the DisplayNumber method is an object type. This is the requirement of this second technique of passing data via start method. The method to which you want to pass the data can have only one parameter and that of type object. And, inside the method you cast that object into a specific type like we did in Example7 where we cast the object num to integer using Convert.ToInt32 method.

    Now come inside the main method, in the constructor of the Thread class you passed a parameter-less method DisplayNumber but when you called the Start method, you passed an integer value to Start method which is actually the parameter to the DisplayNumber method. We passed, 7 which mean that first seven numbers starting from 0 would be displayed in the output.

    Output7

    [​IMG]

    There are two potential drawbacks of this second technique. First is that you can pass only one argument using Start method and secondly since the method which you want to run in thread can have only one parameter of type object, you need to perform explicit casting to use that parameter inside the method.

    Exception Handling in Threads



    Since threads have their own independent path of execution, therefore they cannot be handled in ordinary way by enclosing Thread instantiation in try catch block. You must specify exception handling mechanism inside the method which is due to be run on a thread. Our last example of this tutorial demonstrates this concept. Have a look at the 8th example.

    Example8
    Code:
    using System;
    using System.Threading;
    
    namespace CSharpTutorial 
    {
        class Program 
        {
            public static void Division(int num, int num2) 
            {
                try {
                    Console.WriteLine(num % num2);
                }
                catch (Exception e) {
                    Console.WriteLine("Exception: "+e.Message);
                }
            }
            public static void Main() 
            {
                Thread nt = new Thread(() => Division(10, 0));
                nt.Start();
                Console.ReadLine();
            }
        }
    }
    
    You can see that exception handling has been integrated in the Division method and if you passed second parameter as 0, it will display the exception. In this way we can handle all the exceptions on any thread that executes this Division method. However, if you remove try catch block from the Division method and enclose the Thread instantiation code inside the try catch block as follows:
    Code:
    try {
        Thread nt = new Thread(() => Division(10, 0));
        nt.Start();
    }
    catch (Exception e) {
        Console.WriteLine("Exception: " + e.Message);
    }
    
    The exceptions inside the Division method will not be handled in the above case. The output of the code in Example8 is as follows:

    Output8

    [​IMG]
     
    Last edited: Jan 21, 2017
    Geetanjali Bhor likes this.

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice