o7planning

C# Multithreading Programming Tutorial with Examples

  1. The concept of multithreading
  2. Pass parameters to Thread
  3. Thread uses non-static method
  4. ThreadStart Delegate
  5. Thread with anonymous code
  6. Name the thread
  7. Priority between threads
  8. Using Join()
  9. Using Yield()

1. The concept of multithreading

Multithreading is an important concept in programming languages, and C# too, which is how to make the thread of the program running parallelly to each other. For simplicity you see an example below:
HelloThread.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class HelloThread
    {
        public static void Main(string[] args)
        { 
            Console.WriteLine("Create new Thread...\n");   
            // Creates a child thread, which runs in parallel with the main thread.
            Thread newThread = new Thread(WriteB); 
            Console.WriteLine("Start newThread...\n");

            // Start the thread
            newThread.Start(); 
            Console.WriteLine("Call Write('-') in main Thread...\n");

            // In the main thread print out character '-'
            for (int i = 0; i < 50; i++)
            {
                Console.Write('-'); 
                // Sleep 70 millisenconds.
                Thread.Sleep(70);
            } 
            Console.WriteLine("Main Thread finished!\n");
            Console.Read();
        }  
        public static void WriteB()
        {
            // Print out the 'B' 100 times.
            for (int i = 0; i < 100; i++)
            {
                Console.Write('B');

                // Sleep 100 millisenconds
                Thread.Sleep(100);
            } 
        }
    }  
}
And you run this class:
The operating principle of the thread is explained in the following illustration:

2. Pass parameters to Thread

Above you have become familiar with HelloThread example, you have created an object wrapped a static method to execute this method in parallel with the main thread.
Static method can become a parameter passed into constructor of the Thread class if that method don't have parameters, or with a single parameter object type.
// A static method, have no parameter.
public static void LetGo()
{
      // ...
}

// A static method, has only one parameter type of object.
public static void GetGo(object value)
{
      // ...
}
This next example, I'll create a thread to wrap a static method with one parameter (object type). Running thread and pass value to the parameter.
MyWork.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class MyWork
    { 
        public static void DoWork(object ch)
        {
            for (int i = 0; i < 100; i++)
            {  
                Console.Write(ch); 
                // Sleep 50 milliseconds
                Thread.Sleep(50);
            }
        } 
    }
}
ThreadParamDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class ThreadParamDemo
    { 
        public static void Main(string[] args)
        {  
            Console.WriteLine("Create new thread.. \n");

            // Create a Thread object to wrap around the static method MyWork.DoWork
            Thread workThread = new Thread(MyWork.DoWork); 
            Console.WriteLine("Start workThread...\n");

            // Run workThread,
            // and pass parameter to MyWork.DoWork.
            workThread.Start("*"); 

            for (int i = 0; i < 20; i++)
            {
                Console.Write("."); 
                // Sleep 30 milliseconds.
                Thread.Sleep(30);
            } 
            Console.WriteLine("MainThread ends");
            Console.Read();
        }
    } 
}
Running ThreadParamDemo:

3. Thread uses non-static method

You can also create a thread to execute (using) a none-static method. See for example:
Worker.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace MultithreadingTutorial
{
    class Worker
    {
        private string name;
        private int loop;
        public Worker(string name, int loop)
        {
            this.name = name;
            this.loop = loop;
        }
        public void DoWork(object value)
        {
            for (int i = 0; i < loop; i++)
            {
                Console.WriteLine(name + " working " + value);
                Thread.Sleep(50);
            }
        }
    }
}
WorkerTest.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class WorkerTest
    { 
        public static void Main(string[] args)
        {
            Worker w1 = new Worker("Tom",10);

            // Create a Thread object.
            Thread workerThread1 = new Thread(w1.DoWork);

            // Pass parameter to DoWork method.
            workerThread1.Start("A"); 

            Worker w2 = new Worker("Jerry",15);

            // Create a Thread object.
            Thread workerThread2 = new Thread(w2.DoWork);

            // Pass parameter to DoWork method.
            workerThread2.Start("B");  
            Console.Read();
        }
    } 
}
Running the example:
Tom working A
Jerry working B
Jerry working B
Tom working A
Tom working A
Jerry working B
Tom working A
Jerry working B
Tom working A
Jerry working B
Tom working A
Jerry working B
Tom working A
Jerry working B
Jerry working B
Tom working A
Tom working A
Jerry working B
Jerry working B
Tom working A
Jerry working B
Jerry working B
Jerry working B
Jerry working B
Jerry working B

4. ThreadStart Delegate

ThreadStart is a Delegate, it is initiated by wrap a method. And it is passed as a parameter to initialize the Thread object.
With .Net < 2.0, to start a thread, you need to create ThreadStart, which is a delegate.

Beginning in version 2.0 of the .NET Framework, it is not necessary to create a delegate explicitly. You only need to specify the name of the method in the Thread constructor.
Programmer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class Programmer
    {
        private string name;
        public Programmer(string name)  
        {
            this.name= name;
        } 
        // This is none static method, no parameters.
        public void DoCode()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(name + " coding ... ");
                Thread.Sleep(50);
            } 
        }
    } 
}
ThreadStartDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;


namespace MultithreadingTutorial
{
    class ThreadStartDemo
    { 
        public static void Main(string[] args)
        {
            // Create a ThreadStart object that wrap a static method.
            // (It can only wrap method have no parameters)
            // (ThreadStart is a delegate).        	
            ThreadStart threadStart1 = new ThreadStart(DoWork);

            // Create a thread wrap threadStart1.
            Thread workThread = new Thread(threadStart1);

            // Start thread
            workThread.Start();

            // Create Programmer object.
            Programmer tran = new Programmer("Tran");

            // You can also create a ThreadStart object that wrap a non-static method.
            // (It can only wrap method have no parameters)
            ThreadStart threadStart2 = new ThreadStart(tran.DoCode);

            // Create a Thread wrap threadStart2.
            Thread progThread = new Thread(threadStart2); 
            progThread.Start();

            Console.WriteLine("Main thread ends");
            Console.Read();
        } 
        public static void DoWork()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.Write("*");
                Thread.Sleep(100);
            }                
        }
    } 
}
Running the example:
Main thread ends
*Tran coding ...
Tran coding ...
Tran coding ...
*Tran coding ...
*Tran coding ...
*******

5. Thread with anonymous code

In the above, you have created Thread using a specific method. You can create a thread to execute any code
// Use delegate() to create anonymous method.
delegate()
{
     //  ...
}
Example:
ThreadUsingSnippetCode.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class ThreadUsingSnippetCode
    { 
        public static void Main(string[] args) 
        { 
            Console.WriteLine("Create thread 1"); 
            // Creates a thread to execute a snippet code.
            Thread newThread1 = new Thread(
                delegate()
                {
                    for (int i = 0; i < 10; i++)
                    {
                        Console.WriteLine("Code in delegate() " + i);
                        Thread.Sleep(50);
                    } 
                }
            ); 
            Console.WriteLine("Start newThread1");  
            // Start thread. 
            newThread1.Start(); 
            Console.WriteLine("Create thread 2");

            // Creates a thread to execute a snippet code.
            Thread newThread2 = new Thread(
                delegate(object value)
                {
                    for (int i = 0; i < 10; i++)
                    {
                        Console.WriteLine("Code in delegate(object) " + i + " - " + value);
                        Thread.Sleep(100);
                    } 
                }
            ); 
            Console.WriteLine("Start newThread2");

            // Start thread 2.
            // Pass parameter to delegate().
            newThread2.Start("!!!");  
            Console.WriteLine("Main thread ends");
            Console.Read(); 
        }
    } 
}
Running the example:
Create thread 1
Start newThread1
Create thread 2
Start newThread2
Main thread ends
Code in delegate() 0
Code in delegate(object) 0 - !!!
Code in delegate() 1
Code in delegate() 2
Code in delegate(object) 1 - !!!
Code in delegate() 3
Code in delegate(object) 2 - !!!
Code in delegate() 4
Code in delegate() 5
Code in delegate(object) 3 - !!!
Code in delegate() 6
Code in delegate() 7
Code in delegate(object) 4 - !!!
Code in delegate() 8
Code in delegate() 9
Code in delegate(object) 5 - !!!
Code in delegate(object) 6 - !!!
Code in delegate(object) 7 - !!!
Code in delegate(object) 8 - !!!
Code in delegate(object) 9 - !!!

6. Name the thread

In multi-threaded programming, you can name the stream, it is really useful in case of debugging , to know the code that is being executed in which thread.

In a thread, you can call Thread.CurrentThread.Name to retrieve the name of the thread that take place at that time.

See illustration:
NamingThreadDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class NamingThreadDemo
    { 
        public static void Main(string[] args)
        {
            // Set name to current thread
            // (Main thread)    	
            Thread.CurrentThread.Name = "Main"; 
            Console.WriteLine("Code of "+ Thread.CurrentThread.Name); 
            Console.WriteLine("Create new thread");

            // Create a thread.
            Thread letgoThread = new Thread(LetGo);

            // Set name to thread         
            letgoThread.Name = "Let's Go"; 
            letgoThread.Start();

            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Code of " + Thread.CurrentThread.Name);
                Thread.Sleep(30);
            } 
            Console.Read();
        } 
        public static void LetGo()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Code of " + Thread.CurrentThread.Name);
                Thread.Sleep(50);
            }
        }
    } 
}
Running the example:
Code of Main
Create new thread
Code of Main
Code of Let's Go
Code of Main
Code of Let's Go
Code of Main
Code of Main
Code of Main
Code of Let's Go
Code of Let's Go
Code of Let's Go
Code of Let's Go
Code of Let's Go
Code of Let's Go
Code of Let's Go
Code of Let's Go

7. Priority between threads

there are 5 priority of a thread in C # , they are defined in the ThreadPriority enum .
** ThreadPriority enum **
enum ThreadPriority {
    Lowest,
    BelowNormal,
    Normal,
    AboveNormal,
    Highest
}
Usually with high-speed computers, if the thread only execute little amount of work, you find it very difficult to detect the difference between high-priority threads and threads have low priority.

The example below has two threads, each prints the text of 100K lines (numbers large enough to see the difference).
ThreadPriorityDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class ThreadPriorityDemo
    {
        private static DateTime endsDateTime1;
        private static DateTime endsDateTime2; 
        public static void Main(string[] args)
        {
            endsDateTime1 = DateTime.Now;
            endsDateTime2 = DateTime.Now;

            Thread thread1 = new Thread(Hello1);

            // Set highest priority for thread1
            thread1.Priority = ThreadPriority.Highest; 
            Thread thread2 = new Thread(Hello2);

            // Set the lowest priority for thread2.
            thread2.Priority = ThreadPriority.Lowest; 
            thread2.Start(); thread1.Start(); 
            Console.Read();
        }  
        public static void Hello1()
        {
            for (int i = 0; i < 100000; i++)
            {
                Console.WriteLine("Hello from thread 1: "+ i);
            }
            // The time of thread1 ends.     
            endsDateTime1 = DateTime.Now; 
            PrintInterval();
        } 
        public static void Hello2()
        {
            for (int i = 0; i < 100000; i++)
            {
                Console.WriteLine("Hello from thread 2: "+ i);
            }
            // The time of thread2 ends.
            endsDateTime2 = DateTime.Now; 
            PrintInterval();
        } 
        private static void PrintInterval()
        {
            // Interval (Milliseconds)
            TimeSpan interval = endsDateTime2 - endsDateTime1; 
            Console.WriteLine("Thread2 - Thread1 = " + interval.TotalMilliseconds + " milliseconds");
        }
    } 
}
Running the example:

8. Using Join()

Thread.Join() is a method notifying that please wait for this thread to be completed before the parent thread continues to run.
ThreadJoinDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;


namespace MultithreadingTutorial
{
    class ThreadJoinDemo
    { 
        public static void Main(string[] args)
        {
            Console.WriteLine("Create new thread"); 
            Thread letgoThread = new Thread(LetGo); 
            
            // Start Thread.            
            letgoThread.Start();

            // Tell to the parent thread (here is main thread)
            // wait for the letgoThread to finish, then continue running.
            letgoThread.Join();
            
            // This statement must wait for letgoThread to end, then continue running.
            Console.WriteLine("Main thread ends");
            Console.Read();
        }  
        public static void LetGo()
        {
            for (int i = 0; i < 15; i++)
            {
                Console.WriteLine("Let's Go " + i);
            }
        }
    } 
}
Running the example:
Create new thread
Let's Go 0
Let's Go 1
Let's Go 2
Let's Go 3
Let's Go 4
Let's Go 5
Let's Go 6
Let's Go 7
Let's Go 8
Let's Go 9
Let's Go 10
Let's Go 11
Let's Go 12
Let's Go 13
Let's Go 14
Main thread ends

9. Using Yield()

Theoretically, to ‘yield’ means to let go, to give up, to surrender. A yielding thread tells the operator system that it’s willing to let other threads be scheduled in its place. This indicates that it’s not doing something too critical. Note that it’s only a hint, though, and not guaranteed to have any effect at all.

so, Yield() methods is used when you see that thread is free, it's not doing anything important, it suggests operating system give priority temporarily to the other thread.
The example below, there are two threads, each thread print out a text 100K times (the numbers are large enough to see the difference). One thread is the highest priority, and other thread is lowest priority. See completion time of 2 threads.
ThreadYieldDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class ThreadYieldDemo
    {

        private static DateTime importantEndTime;
        private static DateTime unImportantEndTime;

        public static void Main(string[] args)
        {
            importantEndTime = DateTime.Now;
            unImportantEndTime = DateTime.Now;

            Console.WriteLine("Create thread 1");

            Thread importantThread = new Thread(ImportantWork);

            // Set the highest priority for this thread.
            importantThread.Priority = ThreadPriority.Highest; 

            Console.WriteLine("Create thread 2");

            Thread unImportantThread = new Thread(UnImportantWork);

            // Set the lowest priority for this thread.
            unImportantThread.Priority = ThreadPriority.Lowest; 

            // Start threads. 
            unImportantThread.Start();
            importantThread.Start();
           

            Console.Read();

        }

        // A important job which requires high priority.
        public static void ImportantWork()
        {
            for (int i = 0; i < 100000; i++)
            {
                Console.WriteLine("\n Important work " + i); 

                // Notifying the operating system, 
                // this thread gives priority to other threads.
                Thread.Yield();
            }
            // The end time of this thread.
            importantEndTime = DateTime.Now;
            PrintTime();
        }

        public static void UnImportantWork()
        {
            for (int i = 0; i < 100000; i++)
            {
                Console.WriteLine("\n  -- UnImportant work " + i); 
            }
            // The end time of this thread.           
            unImportantEndTime = DateTime.Now;
            PrintTime();
        }

        private static void PrintTime()
        { 
            // Interval (Milliseconds)
            TimeSpan interval = unImportantEndTime - importantEndTime; 
      
            Console.WriteLine("UnImportant Thread - Important Thread = " + interval.TotalMilliseconds +" milliseconds");
        }
         
    }

}
Running class in the absence of Thread.Yield():
Runs the above class in case the higher priority thread continuously calls Thread.Yield() to request that the system temporarily give priority to the other threads.