Updated date:

Creating Simple Threads in C# and Role of Thread.Join() with Examples

Author:

I am a software engineer. I have been working with C++, MFC, and .net technologies for 15 years. I like video games and reading books.

1. Introduction to Thread

A "Thread" in programming language represents a lightweight version of a process with comparatively small number resources required for its operation. We know that a process is set of "Microprocessor Instruction Sets" and CPU will execute these instruction sets. In modern Multi-Tasking Operating System like windows, there will be more number of processors running in parallel and CPU will execute the instruction sets by allocating some time for each process.

The same "CPU Time Slicing" holds true for Threads as well. Like a process, A thread will have Instruction Sets associated to it and CPU will allocate it’s time for each thread. If there are more than one CPU then there will be chance of Executing instructions from two different thread simultaneously. But, what is more common is that CPU time is allocated for each running process and threads spawned by it.

In this article, we will create an Windows Console Application which explains how we can create thread in C-Sharp. We will also look at the need for "Thread.Join()".

2. Counting Numbers Without Thread

First create C# Console Application and in the Program.cs file add the below code in the static void main function.

//Sample 01: Lets start Two counting in a Loop
//1.1 Declarations
int CountVar1;
int CountVar2;

Here, we are using two variables called CountVar1, CountVar2. These variable are used to keep the running count.

After the variable declaration, we are making call to Console.WriteLine() to write informative text to the console output window. The Console.ReadLine() key is used to read the Enter Button key stroke from the user. This will allow Console Output Window to wait so that the user responds back by hitting the enter key. The code for this below:

//1.2 Inform the User about the Counting
Console.WriteLine("Lets start two counting loops");
Console.WriteLine("Loop1 in Green");
Console.WriteLine("Loop2 in Yellow");
Console.WriteLine("Press Enter(Return) key to continue...");
Console.ReadLine();

After user responds back, we are printing two separate counting and displaying that in the Console Output Window. First we are setting the foreground color of the console output window to Green by setting the ForegroundColor property. The predefined green color is taken from the ConsoleColor enumaration.

Once the console color is set to Green, we are running a For Loop and printing the counting that goes till 999. Next, we are setting the Console Windows output color to Yellow and starting the second loop to print the counting from 0 to 999. After this we are resetting the Console window to its original state. The code is below:

//1.3 Start Counting in the Main Thread
Console.WriteLine("Main Thread - Starts Counting");
Console.ForegroundColor = ConsoleColor.Green;
for (CountVar1 = 0;
    CountVar1 < 1000;
    CountVar1++)
{
    Console.WriteLine("CountVar1: " +
        CountVar1.ToString());
}
Console.ForegroundColor = ConsoleColor.Yellow;
for (CountVar2 = 0;
    CountVar2 < 1000;
    CountVar2++)
{
    Console.WriteLine("CountVar2: " + 
        CountVar2.ToString());
}
Console.ResetColor();
Console.WriteLine("Main Thread - After Counting Loops");

The two loops execution in the Main Thread context is shown in the below picture:

Two counting loops in Main Thread Context

Two counting loops in Main Thread Context

The picture above shows that CountVar1 loop is entered first and start counting the variables and displays in the Console Windows. And, the time taken for that is T1 milliseconds. The CountVar2 will wait for the exit of CountVar1 loop. Once CountVar1 loop exits, the CountVar2 loop starts and displays the output by taking T2 milliseconds. Here, the counting loops are sequential and this can be proved by the program output at this stage. Run the Program as shown below from the command prompt:

Run the SimpleThread from command line

Run the SimpleThread from command line

The output of the program execution is shown below (The output is broken into three pieces)

Program Output: Loop Counting without Thread

Program Output: Loop Counting without Thread

In the above output, we can see that the loops executed sequentially and yellow color console output can be seen only after the Green (First Loop) one.

3. Loop Counting Functions for Thread

Now, we will move the loop counting to two different functions and assign each to a dedicated thread later. First, have a look at these functions:

//Sample 2.0: Counting functions used by Thread
//2.1: Counting Function for Thread 1
public static void CountVar1_Thread()
{
    for (int CountVar1 = 0; 
        CountVar1 < 1000; 
        CountVar1++)
    {
        Console.ForegroundColor = 
            ConsoleColor.Green;
        Console.WriteLine("CountVar1: " 
            + CountVar1.ToString());
    }
}

//2.2: Counting Function for Thread 2
public static void CountVar2_Thread()
{
    for (int CountVar2 = 0; 
        CountVar2 < 1000; 
        CountVar2++)
    {
        Console.ForegroundColor = 
            ConsoleColor.Yellow;
        Console.WriteLine("CountVar2: " 
            + CountVar2.ToString());
    }
}

In the above code you can see that counting is similar to what we have seen previously. The two loops are converted into two different functions. However, you can see setting the ForgroundColor of Console Window is done inside the loop for a purpose.

Previously, we saw that the loops executed sequentially and now, we are going to allocate a thread for each function and the CPU will apply "Time slicing" (Try to execute instruction sets from both the function by scheduling its time. Nano Seconds?) so that it pays attention to both the loops. That is the CPU spend some of its time with First Function and some with Second Function while doing the counting.

Keeping those in mind in addition with both function accessing the same resource (console window), the Foreground color setting is done inside loop. This will 99% shows First function output in Green color and Second function output in Yellow color. What about 1% error? We have to learn Thread Synchronization for that. And, we will see that in a different article.

4. Creating Simple Threads and Starting It

To use thread in this example, a namespace is included and the code is shown below:

//Sample 03: NameSpace Required for Thread
using System.Threading;

In the Main function using Console.WriteLine(), informative message is given to the user. The thread start begins, once user hits the Enter Key button. Code is below:

//Sample 4.0: Start Two Counting Loops 
//              in a separate thread
Console.WriteLine("Lets start two counting" + 
                   " loops in Threads");
Console.WriteLine("Thread1 in Green");
Console.WriteLine("Thread2 in Yellow");
Console.WriteLine("Press Enter(Return) key " + 
    "to continue...");
Console.ReadLine();

After the informative message we are creating two threads called T1 and T2 by supplying the static threaded functions created earlier. Have a look at the code below:

//4.1 Create Two Separate Threads
Console.WriteLine("Main Thread - Before Starting Thread");
Thread T1 = new Thread(
    new ThreadStart(CountVar1_Thread));
Thread T2 = new Thread(
    new ThreadStart(CountVar2_Thread));

The above code snippet can be explained through the depiction below.

Creating Simple Threads in C#

Creating Simple Threads in C#

In the above picture Marker 1 shows that we are holding the reference to the thread instance T1 of type “Thread”. Marker 2 shows that we are creating the “ThreadStart” delegate and supplying that to the constructor of the Thread class. Also note that we are creating the delegate by providing the function that runs on this thread T1. The same way we are making the CountVar2_Thread() function to run on Thread instance T2.

Finally we are starting the Threads by calling Start() method. The start method then invoke the delegate to call the supplied function. Now the function runs the thread which is started by "Start()" method call. Have a look at the code below:

//4.2 Start the Threads
T1.Start();
T2.Start();
Console.WriteLine("Main Thread - After Starting Threads");
Console.ResetColor();

In the above code snippet, we are starting two threads T1 and T2. After starting the Thread, we are printing an informative message in the Console Window. Note that the Main thread (The Main() function is running on the "Main Application Thread") spawned two Threads called T1 and T2. Now CountVar1_Thread() function is executed on Thread T1 and CountVar2_Thread() is executed on Thread T2. The timing of execution can be explained through the picture below:

Thread Timing Chart - (Simulated one for Explanation)

Thread Timing Chart - (Simulated one for Explanation)

The above timing chart shows that Main thread started the Thread T1 first and then Thread T2. After certain point in time, we can say that all three threads (Main, T1, T2) are served by the CPU by means of executing the instruction sets involved in it. This time period (All three threads are busy) is shown as yellow block. While thread T1 and T2 are busy in counting the numbers & spitting it on the console window, Main thread quits after printing the Resetting Console Window message. We can see a problem here. The intention is to reset the console window Foreground color to its original state after T1 and T2 finishes. But, the Main Thread continues its execution after spawning the thread and quits before T1 and T2 exits (Time t1 is well ahead of t2 & t3).

The Console.ResetColor();called by the Main thread is overwritten by T1 and T2 and whichever thread finishes last leaves the console window with the foreground color set by it. In the above picture, we can see even though Main thread halts at time t1, Thread T1 continues till t2 and Thread T2 continues till t3. The green block shows T1 and T2 execution happening in parallel. We actually don’t know which thread will finish first (T1 or T2?). When all thread quits, Operating system removes the program from memory.

Have a look at the output of the program:

Program Output: Counter Threads

Program Output: Counter Threads

The above output shows that Green thread (T1) finished counting first. And yellow thread finished last. The "dir command" lists the directory in yellow color as the Reset Console window done by Main thread is overwritten by the T1 and T2 multiple time.

5. Thread.Join() - The Calling Thread Waits...

The "Join()" method is useful to wait till other thread finishes the Task. Have a look at the code below:

//4.3 Reset the Console Window
T1.Join();
T2.Join();
Console.ResetColor();

The main thread calling T1.Join() states that main thread will wait till T1 finishes. The same way T2.Join() ensures that main thread will till T2 finishes the job. When we call both T1.Join(); T2.Join(), main thread will till T1 and T2 finishes their counting. Look at the last line of code Console.ResetColor(). It is safe now Right?

The complete code example is given below:

using System;
using System.Collections.Generic;
using System.Text;

//Sample 03: NameSpace Required for Thread
using System.Threading;

namespace SimpleThread
{
    class Program
    {
        //Sample 2.0: Counting functions used by Thread
        //2.1: Counting Function for Thread 1
        public static void CountVar1_Thread()
        {
            for (int CountVar1 = 0; 
                CountVar1 < 1000; 
                CountVar1++)
            {
                Console.ForegroundColor = 
                    ConsoleColor.Green;
                Console.WriteLine("CountVar1: " 
                    + CountVar1.ToString());
            }
        }

        //2.2: Counting Function for Thread 2
        public static void CountVar2_Thread()
        {
            for (int CountVar2 = 0;
                CountVar2 < 1000;
                CountVar2++)
            {
                Console.ForegroundColor =
                    ConsoleColor.Yellow;
                Console.WriteLine("CountVar2: "
                    + CountVar2.ToString());
            }
        }

        static void Main(string[] args)
        {
            //Sample 01: Lets start Two counting in a Loop
            //1.1 Declarations
            int CountVar1;
            int CountVar2;

            //1.2 Inform the User about the Counting
            Console.WriteLine("Lets start two counting loops");
            Console.WriteLine("Loop1 in Green");
            Console.WriteLine("Loop2 in Yellow");
            Console.WriteLine("Press Enter(Return) key to continue...");
            Console.ReadLine();

            //1.3 Start Counting in the Main Thread
            Console.WriteLine("Main Thread - Starts Counting");
            Console.ForegroundColor = ConsoleColor.Green;
            for (CountVar1 = 0;
                CountVar1 < 1000;
                CountVar1++)
            {
                Console.WriteLine("CountVar1: " +
                    CountVar1.ToString());
            }
            Console.ForegroundColor = ConsoleColor.Yellow;
            for (CountVar2 = 0;
                CountVar2 < 1000;
                CountVar2++)
            {
                Console.WriteLine("CountVar2: " +
                    CountVar2.ToString());
            }
            Console.ResetColor();
            Console.WriteLine("Main Thread - After Counting Loops");

            //Sample 4.0: Start Two Counting Loops 
            //              in a separate thread
            Console.WriteLine("Lets start two counting" +
                               " loops in Threads");
            Console.WriteLine("Thread1 in Green");
            Console.WriteLine("Thread2 in Yellow");
            Console.WriteLine("Press Enter(Return) key " +
                "to continue...");
            Console.ReadLine();

            //4.1 Create Two Separate Threads
            Console.WriteLine("Main Thread - Before Starting Thread");
            Thread T1 = new Thread(
                new ThreadStart(CountVar1_Thread));
            Thread T2 = new Thread(
                new ThreadStart(CountVar2_Thread));

            //4.2 Start the Threads
            T1.Start();
            T2.Start();
            Console.WriteLine("Main Thread - After Starting Threads");

            //4.3 Reset the Console Window
            T1.Join();
            T2.Join();
            Console.ResetColor();
        }
            
    }
}

© 2018 sirama

Related Articles