Lifecycle of Threads

 < Day Day Up > 



When a thread is scheduled for execution it can go through several states, including unstarted, alive, sleeping, etc. The Thread class contains methods that allow you to start, stop, resume, abort, suspend, and join (wait for) a thread. We can find the current state of the thread using its ThreadState property, which will be one of the values specified in the ThreadState enumeration:

  • Aborted - The thread is in the stopped state, but did not necessarily complete execution

  • AbortRequested - The Abort() method has been called but the thread has not yet received the System.Threading.ThreadAbortexception that will try to terminate it - the thread is not stopped but soon will be.

  • Background - The thread is being executed in the background

  • Running - The thread has started and is not blocked

  • Stopped - The thread has completed all its instructions, and stopped

  • StopRequested - The thread is being requested to stop

  • Suspended - The thread has been suspended

  • SuspendRequested - The thread is being requested to suspend

  • Unstarted - The Start() method has not yet been called on the thread

  • WaitSleepJoin - The thread has been blocked by a call to Wait(), Sleep(), or Join()

Figure 3 shows the lifecycle of a thread. Figure 3.

click to expand
Figure 3

In this section, we'll explore the lifecycle of threads.

Putting a Thread to Sleep

When we create a new thread we have to call the Start() method of the Thread object to schedule that thread. At this time, the CLR will allocate a time slice to the address of the method passed to the constructor of the Thread object. Once the thread is in the Running state, it can go back to either the Sleep or Abort states when the OS is processing the other threads. We can use the Sleep() method of the Thread class to put a thread to sleep. The Sleep() method is really useful if you are waiting for a resource and you want to retry for it. For example, let's say your application cannot proceed due to unavailability of a resource that it is trying to access. You may want your application to retry to access the resource after few milliseconds, in which case the Sleep() method is a good way to put the thread to sleep for a specified time before the application retries to access the resource.

The overloaded Sleep() method is available in two flavors. The first overload takes an integer as the parameter that will suspended the thread for number of milliseconds specified. For example, if you pass 100 to the parameter the thread will be suspended for 100 milliseconds. This will place the thread into the WaitSleepJoin state. Let's see an example for this, thread_sleep2.cs:

    using System;    using System.Threading;    public class ThreadSleep    {       public static Thread worker;       public static Thread worker2;       public static void Main()       {          Console.WriteLine("Entering the void Main!");          worker = new Thread(new ThreadStart(Counter));          worker2 = new Thread(new ThreadStart(Counter2));          // Make the worker2 object as highest priority          worker2.Priority = System.Threading.ThreadPriority.Highest;          worker.Start();          worker2.Start();          Console.WriteLine("Exiting the void Main!");       }       public static void Counter()       {          Console.WriteLine("Entering Counter");          for(int i = 1; i < 50; i++)          {             Console.Write(i + " ");             if(i == 10)                Thread.Sleep(1000);          }          Console.WriteLine();          Console.WriteLine("Exiting Counter");       }       public static void Counter2()       {          Console.WriteLine("Entering Counter2");          for(int i = 51; i < 100; i++)          {             Console.Write(i + " ");              if( i == 70 )                Thread.Sleep(5000);          }          Console.WriteLine();          Console.WriteLine("Exiting Counter2");       }    } 

The Counter() method counts numbers from 1 to 50 and when it reaches 10 it sleeps for 1000 milliseconds. The Counter2() method counts from 51 to 100 and when it reaches 70 it sleeps for 5000 milliseconds. Here is how the output might look:

    Entering the void Main!    Entering Counter2    51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 Exiting    the void Main!    Entering Counter    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26    27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49    50    Exiting Counter    71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93    94 95 96 97 98 99 100    Exiting Counter2 

The second overload takes a TimeSpan as parameter and, based on the TimeSpan value, the current thread will be suspended. The TimeSpan is a structure defined in the System namespace. The TimeSpan structure has a few useful properties that return the time interval based on clock ticking. We can use public methods such as FromSeconds() and FromMinutes() to specify the sleep duration. Here is an example, thread_sleep3.cs:

      public static void Counter()      {        ...        for(i = 1; i < 50; i++)        {          Console.Write(i + " ");          if(i == 10)            Thread.Sleep(System.TimeSpan.FromSeconds(1))        }        ...      }       public static void Counter2()       {          ...          for(int i = 51; i < 100; i++)          {             Console.Write(i + " ");             if( i == 70 )                Thread.Sleep(5000);          }          ...       } 

The output will be similar to that of thread_sleep2.

Interrupting a Thread

When a thread is put to sleep, the thread goes to the WaitSleepJoin state. If the thread is in the sleeping state the only way to wake the thread, before its timeout expires, is using the Interrupt() method. The Interrupt() method will place the thread back in the scheduling queue. Let's see an example for this, thread_interrupt.cs:

    using System;    using System.Threading;    public class Interrupt    {       public static Thread sleeper;       public static Thread worker;       public static void Main()       {          Console.WriteLine("Entering the void Main!");          sleeper = new Thread(new ThreadStart(SleepingThread));          worker = new Thread(new ThreadStart(AwakeTheThread));          sleeper.Start();          worker.Start();          Console.WriteLine("Exiting the void Main!");       }       public static void SleepingThread()       {          for(int i = 1; i < 50; i++)          {             Console.Write(i + " ") ;             if(i == 10 || i == 20 || i == 30)             {                Console.WriteLine("Going to sleep at: " + i);                Thread.Sleep(20);             }          }       }       public static void AwakeTheThread()       {          for(int i = 51; i < 100; i++)          {             Console.Write(i + " ");             if(sleeper.ThreadState ==                System.Threading.ThreadState.WaitSleepJoin)             {                Console.WriteLine("Interrupting the sleeping thread");                sleeper.Interrupt();             }          }       }    } 

In the above example, the first thread (sleeper) is put to sleep when the counter reaches 10, 20, and 30. The second thread (worker) checks if the first thread is asleep. If so, it interrupts the first thread and places it back in the scheduler. The Interrupt() method is the best way to bring the sleeping thread back to life and you can use this functionality if the waiting for the resource is over and you want the thread to become alive. The output will look similar to the following:

    Entering the Sub Main!    Exiting the Sub Main!    51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73    74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96    97 98 99 100 1 2 3 4 5 6 7 8 9 10 Going to sleep at: 10    11 12 13 14 15 16 17 18 19 20 Going to sleep at: 20    21 22 23 24 25 26 27 28 29 30 Going to sleep at: 30    31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 

Pausing and Resuming Threads

The Suspend() and Resume() methods of the Thread class can be used to suspend and resume the thread. The Suspend() method will suspend the current thread indefinitely until another thread wakes it up. When we call the Suspend() method, the thread will be place in the SuspendRequested or Suspended state.

Let's see an example for this. We'll create a new C# application that generates prime numbers in a new thread. This application will also have options to pause and resume the prime number generation thread. To make this happen let's create a new C# WinForms project called PrimeNumbers and build a UI like this in Form1.

click to expand

We have a ListBox and three command buttons in the UI. The ListBox is used to display the prime numbers and three command buttons are used to start, pause, and resume the thread. Initially we've disabled the pause and the resume buttons, since they can't be used until the thread is started. Let's see what the code is going to look like. We've declared a class-level Thread object that is going to generated prime numbers.

    using System;    using System.Drawing;    using System.Collections;    using System.ComponentModel;    using System.Windows.Forms;    using System.Threading;    namespace Chapter_02    {      public class Form1 : System.Windows.Forms.Form      {        // private thread variable        private Thread primeNumberThread; 

Double-click on the Start command button and add the following code.

        private void cmdStart Click(object sender, System.EventArgs e)        {          // Let's create a new thread          primeNumberThread = new Thread(            new ThreadStart(GeneratePrimeNumbers));          // Let's give a name for the thread          primeNumberThread.Name = "Prime Numbers Example";          primeNumberThread.Priority = ThreadPriority.BelowNormal;          // Enable the Pause Button          cmdPause.Enabled = true;          // Disable the Start button          cmdStart.Enabled = false;          // Let's start the thread          primeNumberThread.Start();        } 

All the Start button does is create a new Thread object with the ThreadStart delegate of the GeneratePrimeNumbers() method and assign the name Prime Number Example to the thread. Then it enables the Pause button and disables the Start button. Then it starts the prime number generating thread using the Start method of the Thread class.

Let's double-click on the Pause button and add the following code.

        private void cmdPause Click(object sender, System.EventArgs e)        {          try          {          try          {            // If current state of thread is Running,            // then pause the Thread            if (primeNumberThread.ThreadState ==              System.Threading.ThreadState.Running)            {              //Pause the Thread              primeNumberThread.Suspend();              //Disable the Pause button              cmdPause.Enabled = false;              //Enable the resume button              cmdResume.Enabled = true;            }          }          catch(ThreadStateException Ex)          {            MessageBox.Show(Ex.ToString(), "Exception",              MessageBoxButtons.OK, MessageBoxIcon.Error,              MessageBoxDefaultButton.Button1);          }         } 

The Pause button checks if the thread is in the Running state. If it is in the Running state, it pauses the thread by calling the Suspend method of the Thread object. Then it enables the Resume button and disables the Pause button. Since the Suspend method can raise the ThreadStateException exception, we're wrapping the code with in a try...catch block.

Double-click on the Resume button and add the following code.

        private void cmdResume Click(object sender, System.EventArgs e)        {          if(primeNumberThread.ThreadState ==            System.Threading.ThreadState.Suspended ||            primeNumberThread.ThreadState ==            System.Threading.ThreadState.SuspendRequested)          {            try            {              // Resume the thread              primeNumberThread.Resume();              // Disable the resume button              cmdResume.Enabled = false;              // Enable the Pause button              cmdPause.Enabled = true;            }            catch(ThreadStateException Ex)            {              MessageBox.Show(Ex.ToString(), "Exception",                MessageBoxButtons.OK, MessageBoxIcon.Error,                MessageBoxDefaultButton.Button1);            }          }        } 

The Resume button checks if the state of the thread is Suspended or SuspendRequested before resuming the thread. If the state of the thread is either Suspended or SuspendRequested then it resumes the thread and disables the Resume button and enables the Pause button.

Well, so far our business logic is ready. Let's see the code that generates the prime numbers. Since our main aim is to use multithreading and not prime number generation, I'm not going to go deep into the code. The GeneratePrimeNumbers() method generates the first 255 prime numbers starting from 3. When the method finds a prime number it'll add the new prime number to an array as well as to the listbox. The first prime number, 2, will be automatically added to the listbox. Finally, the method will enable the Start button and disable the Pause button.

        public void GeneratePrimeNumbers()        {          long lngCounter;          long lngNumber;          long lngDivideByCounter;          bool blnIsPrime;          long[] PrimeArray = new long[256];          // initialize variables          lngNumber = 3;          lngCounter = 2;          // We know that the first prime is 2. Therefore,          // let's add it to the list and start from 3          PrimeArray[1] = 2;          lstPrime.Items.Add(2);          while(lngCounter < 256)          {            blnIsPrime = true;            // Try dividing this number by any already found prime            // which is smaller then the root of this number.            for(lngDivideByCounter = 1; PrimeArray[lngDivideByCounter]                * PrimeArray[lngDivideByCounter] <= lngNumber;              lngDivideByCounter++)            {              if(lngNumber % PrimeArray[lngDivideByCounter] == 0)              {                // This is not a prime number                blnIsPrime = false;                // Exit the loop break;              }            }            // If this is a prime number then display it            if(blnIsPrime)            {              // Guess we found a new prime.              PrimeArray[lngCounter] = lngNumber;              // Increase prime found count.              lngCounter++;              lstPrime.Items.Add(lngNumber);              // Let's put the thread to sleep for 100 milliseconds.              // This will simulate the time lag and we'll get time              // to pause and resume the thread              Thread.Sleep(100);            }            // Increment number by two            lngNumber += 2;          }          // Once the thread is finished execution enable the start          // and disable the pause button          cmdStart.Enabled = true;          cmdPause.Enabled = false;        } 

Well everything is ready now. Let's run the code and see how our application looks.

click to expand

Well, everything looks good now and the code is apparently working fine. But there is a huge flaw in our code. When the GeneratePrimeNumbers() method finds a prime number it adds the prime number back to the listbox control. It may not look like a problem for you if this code is running in a synchronized execution manner where both the prime number generation code and the user interface are running on the same thread. But in our example, the UI is running in a different thread from the GeneratePrimeNumbers() method. Therefore, when we go between threads to write data this could cause some unexpected behaviors in our application.

The best way to address this problem is using delegates. We can declare a delegate and we can use the delegate to inform the UI thread to update the listbox control itself. In this way, we're not crossing the threading boundaries and the application stability is not compromised.

Let's see how we can implement this operation. Let's add one more public delegate called UpdateData:

      public delegate void UpdateData(string returnVal); 

Let's modify the GeneratePrimeNumbers() method a little bit to call the delegate from it. We've added a new string array with the initial value as 2, since the first prime number is 2. Then we've declared a new object of the type UpdateData as a delegate and we've passed the address of the UpdateUI method. Then we've used the this. Invoke method with the delegate object and the string array to inform the user interface to update itself. We've done the same when we found a prime number.

Form1 is represented as this in this context.

    public void GeneratePrimeNumbers()    {      long lngCounter;      long lngNumber;      long lngDivideByCounter;      bool blnIsPrime;      long[] PrimeArray = new long[255] ;      string[] args = new string[] {"2"};      UpdateData UIDel = new UpdateData(UpdateUI);      ...      this.Invoke(UIDel, args);      while(lngCounter <= 255)      {        ...       // If this is a prime number then display it        if(blnIsPrime)        {          // Guess we found a new prime.          PrimeArray[lngCounter] = lngNumber;          // Increase prime found count.          lngCounter++;          args[0] = lngNumber.ToString();          this.Invoke(UIDel, args);          ...        }        ...      }      ...    } 

The UpdateUI() method simply accepts the value that needs to be added to the listbox in its parameter and adds the value to the listbox. Since the UpdateUI method runs in the UI thread there are no cross-boundary thread updates and the stability of our application is not compromised:

    void UpdateUI(string result )    {      lstPrime.Items.Add(result);    } 

Destroying Threads

The Abort() method can be used to destroy the current thread. The Abort() method would be very useful, if you want to terminate the thread for whatever reason, such as the thread is taking too much time to execute or the user has changed their mind by selecting cancel. You might want this behavior in a search process that takes a long time. A search may continue running but the user may have seen the results they wish to see and discontinue the thread that is carrying on the search routine. When Abort() is called against a thread, the ThreadAbortException exception will be raised. If it isn't caught in any code in the thread, then the thread will terminate. Think twice before writing generic exception handling code inside methods that will be accessed in a multithreaded context, since a catch (Exception e) will also catch ThreadAbortExceptions - from which you probably don't want to recover. As we'll see, ThreadAbortException isn't so easily stopped, and your program flow may not continue as you expect it to.

Let's see an example for this. We're going to create a new project called Destroying and we'll copy the code from the previous prime number generation code into the new Form1.cs class. Let's add one more Stop button to the UI like this.

click to expand

Let's add the following code into the Stop button.

    private void cmdStop Click(object sender, System.EventArgs e)    {      // Enable the Start button and disable all others      cmdStop.Enabled = false;      cmdPause.Enabled = false;      cmdResume.Enabled = false;      cmdStart.Enabled = true;      // Destroy the thread      primeNumberThread.Abort();    } 

This example is very similar to the previous example. The only difference is that we're using the Abort() method to destroy the thread when the user clicks on the Stop button. Then we're enabling the Start button and disabling all other buttons. You might also note that the ThreadAbortException is a special exception. Like all other exceptions it can be caught. However, once the catch block has completed, the exception will automatically be raised again. When the exception is raised, the runtime executes all the finally blocks before killing the thread.

Joining Threads

The Join() method blocks a given thread until the currently running thread is terminated. When we call the Join() method against a given thread instance, the thread will be placed in the WaitSleepJoin state. This method is very useful, if one thread is dependent upon another thread. By simply joining two threads we are saying that the thread that is running when the Join() method is called will enter the WaitSleepJoin state and not return to the Running state until the thread upon which the Join() method was called completes its tasks. This may sound a bit confusing, but let's see an example for this in the following code sample, thread_joining.cs:

    using System;    using System.Threading;    namespace Chapter_02    {      public class JoiningThread      {        public static Thread SecondThread;        public static Thread FirstThread;        static void First()        {          for(int i = 1; i <= 250; i++)            Console.Write(i + " ");        }        static void Second()        {          FirstThread.Join();          for(int i = 251; i <= 500; i++)            Console.Write(i + " ");        }        public static void Main()        {          FirstThread = new Thread(new ThreadStart(First));          SecondThread = new Thread(new ThreadStart(Second));          FirstThread.Start();          SecondThread.Start();        }      }    } 

In this simple example, the aim is to output numbers to the console sequentially, starting at 1 and finishing at 500. The First() method will output the first 250 numbers and the Second() method will produce those from 251 to 500. Without the FirstThread.Join() line in the Second() method, execution would switch back and forth between the two methods and our output would be scrambled (try commenting out the line and running the example again). By calling the FirstThread.Join() method within the Second() method, the execution of the Second() method is paused until the execution of whatever is in FirstThread (the First() method) has completed.

The Join() method is overloaded; it can accept either an integer or a TimeSpan as a single parameter and returns a Boolean. The effect of calling one of the overloaded versions of this method is that the thread will be blocked until either the other thread completes or the time period elapses, whichever occurs first. The return value will be true if the thread has completed and false if it has not.



 < Day Day Up > 



C# Threading Handbook
C# Threading Handbook
ISBN: 1861008295
EAN: 2147483647
Year: 2003
Pages: 74

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net