Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Journey into Multi Threading

4.46/5 (6 votes)
17 May 2010CPOL3 min read 30.7K   1.6K  
Threading tutorial and examples for beginners

Screenshot.PNG

Introduction

Everyone knows that free lunch is over. Next generation applications are designed to exploit the power of processors. The challenge is to build software to react to the user's activities as rapidly as possible to provide a rich user experience and at same time, doing the necessary computations to present data to the user as fast as possible. And this is one case where multithreading is useful. There are other cases where multithreading is useful, like:

  1. Performing operations that take a long time.
  2. When tasks have varying priorities. Set thread as high-priority to take the time critical tasks and low priority thread performs the other tasks.
  3. When you want to monitor some resources continuously.

So, there is a lot of benefit for developers to write applications making use of multithreading. As we know, threading is a vast topic and cannot fit in a single article. I am considering a series of articles describing various topics related to multi-threading. As of now, these topics are not yet decided. So just relax, we are about to start the journey into building multi-threading applications.

Creating a New Thread

One can create a thread by using:

C#
Thread thread = new Thread(FunctionName);

First, let's create a console application. Here is the self documented code snippet:

C#
class Program
    {
        static void Main(string[] args)
        {
            //Main Thread of the application is Named as "Main Thread"
            Thread.CurrentThread.Name = "Main Thread";
            Console.WriteLine("From Main() function\n");
            //printing Main thread name and ID.
            //One can get the ID of the thread by enquiring 
            //"Thread.CurrentThread.ManagedThreadId"
            Console.WriteLine("Thread Name:{0}\nThread ID:{1}\n",
		Thread.CurrentThread.Name,Thread.CurrentThread.ManagedThreadId);
            
            //Create an Object of the class Compute, 
            //through which we can access Instance Method.
            Compute compute = new Compute();            
            Thread Th1 = new Thread(Compute.StaticMethod); //new thread Th1 
					//is created by passing static method.
            Th1.Name = "Th1"; //Let's Name the thread as Th1.
            Th1.Start(); // Let's start the Thread.

            Thread Th2 = new Thread(compute.InstanceMethod);
            Th2.Name = "Th2";
            Th2.Start();

            Console.ReadLine();
        }
    }

    class Compute
    {
        public Compute()
        {
            Console.WriteLine("Initialized the Compute class");
            Console.WriteLine("Thread ID:{0}\nThread Name:{1}\n", 
		Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Name);
        }

        public static void StaticMethod()
        {
            Console.WriteLine("From the Static Method in the Compute class");
            Console.WriteLine("Thread ID:{0}\nThread Name:{1}\n", 
		Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.Name);
            Thread.Sleep(200); // thread which is now running 
			// Static method is made to sleep for 200 micro Seconds.
        }

        public void InstanceMethod()
        {
            Console.WriteLine("From the Instance Method in the Compute class");
            Console.WriteLine("Thread ID:{0}\nThread Name:{1}\n", 
		Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Name);
            Thread.Sleep(200);
        }

        public void DataMethod(object Message)
        {
            Console.WriteLine("From the Instance Data Method in the Compute class");
            string s = Message as string;

            if (s != null)
            {
                Console.WriteLine("Data Received :{0}", s);
                Console.WriteLine("Thread ID:{0}\nThread Name:{1}\n", 
		Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Name);
            }
        }
    }

Thread constructor is overloaded, thread class has constructors that take a ThreadStart delegate or ParameterizedThreadStart delegate; the delegate wraps the method that is invoked by the new thread when you call the Start method. Here is the code snippet showing only Threads creation using ThreadStart delegate. Compute class remains the same.

C#
class Program
    {
        static void Main(string[] args)
        {
            Thread.CurrentThread.Name = "Main Thread";
            Console.WriteLine("From Main() function\n");

            //Create a ThreadStart Delegate object and pass the function.
            ThreadStart tStartStatic = new ThreadStart(Compute.StaticMethod);
            //Create the thread
            Thread Th1 = new Thread(tStartStatic);
            Th1.Name = "Th1"; 	//Set Name.
            Th1.Start(); 		//Start the thread.

            Compute compute = new Compute();
            ThreadStart tStartInstance = new ThreadStart(compute.InstanceMethod);
            Thread Th2 = new Thread(tStartInstance);

            Th2.Start();
            Th2.Name = "Th2"; 	// Note: Named the thread after Thread start. 
				// will Name be set to Th2 ?
            Console.ReadLine();
        }
    }

Here is the code snippet for creating thread using "ParameterizedThreadStart".

C#
class Program
    {
        static void Main(string[] args)
        {
            Thread.CurrentThread.Name = "Main Thread";
            Console.WriteLine("From Main() function\n");

            Compute compute = new Compute();
            //Delegate object is created and is pointed to a function. 
            ParameterizedThreadStart pThreadStart = 
		new ParameterizedThreadStart(compute.DataMethod);

            // A new thread is created with the delegate.
            Thread thread = new Thread(pThreadStart);
            thread.Name = "Parameterized Thread"; // set the Name for the thread.
            thread.Start("Hello World"); //Pass the arguments  to thread.start function.
            Console.ReadLine();
        }
    }

Let's have one callback delegate which is triggered when thread completes its work. Here is the code snippet:

C#
namespace ConAppThreadingEx2
{
    class Program
    {
        static void Main()
        {
            //Name the thread.
            Thread.CurrentThread.Name = "Main Thread";
            Console.WriteLine("Thread Name:{0}\nThread ID:{1}\n", 
		Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);

            //Create an object of CallBackDelegate type and point to 
            //ResultFromThread function.
            CallBackDelegate cb = new CallBackDelegate(ResultFromThread);
            //Create an object of type Compute, look at delegate object 
            //is used in constructor.
            Compute compute = new Compute(50, cb);
            //Create a thread. No difference, just a one more variation.
            Thread t = new Thread(new ThreadStart(compute.ThreadPro));
            t.Name = "ThreadPro"; //set the name for the thread.
            t.Start(); // Start the thread.
            Console.Read();
        }

        //this function is called when thread completes its work.
        static void ResultFromThread(int i)
        {
            Console.WriteLine
	    ("Thread just returned from the ThreadPro method performing:{0} Tasks", i);
        }
    }

    public delegate void  CallBackDelegate(int i);

    class Compute
    {
        int numberOfTasks;
        CallBackDelegate CallBackDlg;

        public Compute(int Tasks, CallBackDelegate cb)
        {
            numberOfTasks = Tasks;
            CallBackDlg = cb;
        }

        //Thread is pointed to this function. Delegate is called 
        //at the end of the function.
        //Which triggers the ResultFromThread function.
        public void ThreadPro()
        {
            Console.WriteLine("From ThreadPro Method in Compute Class");
            Console.WriteLine("Thread Name:{0}\nThread ID:{1}\n",
		Thread.CurrentThread.Name,Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(2000); // simulating numberOfTasks
            //check for CallBackDlg and trigger.
            if (CallBackDlg != null)
                CallBackDlg(numberOfTasks);
        }
    }
}

Let’s put all this learning into a bigger example – where we use a GUI application to demonstrate the creation, starting, pausing, resuming and stopping of a thread. Here, a new thread is created to draw the image. This is an example where a color image is converted to grayscale; here the conversion is deliberately made slow since the main purpose is to demonstrate threading – and the user can visually see the pausing, resuming and stopping of threads. The interesting part of the application is thread suspend and thread resume and also communication between thread and UI thread using this.Invoke().

Controls like button, text box, label, etc. are created by UI thread. When a new thread is created and during the work flow on a new thread, the new thread will not have any control over these control objects on UI, because these are created by UI thread. There is a solution for such situations where worker thread can call Invoke() function on a control which in turn triggers the function through a delegate. The next interesting part of the application is suspending, resuming and aborting of a thread. Aborting a thread, when thread is running (check the ThreadState for its state) is straight forward. But when thread is suspended or request for suspended is the thread state, in such case, thread abort may result in crash.

The CLR will not abort a suspended thread but it will take thread to a state where Garbage Collector can reclaim the memory. So, the best solution would be resume the suspended thread and abort the thread.

Take a look at the ThreadingA source code.

History

  • Version 1.0.0.0

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)