17.2 Thread states and multi-threading in C


17.2 Thread states and multi-threading in C#

In C#, a thread can exist in the states shown in Figure 17.2. A thread must be in one (and only one) of these seven states at any one time.

Figure 17.2. Possible thread states in C#.

graphics/17fig02.gif

Some Java books may categorize threads which are 'ready to run' in a separate state, so that there are 'ready to run' threads (also known as 'runnable'), and 'running' threads. In a single-CPU machine, only one thread can be in the running state, while all the rest are in the ready to run pool. In Figure 17.2, I have not differentiated between ready to run and running. All threads which are ready to run, are considered to be in the running state. However, only one of these can actually be hogging the CPU in a single-CPU machine at any one time.

Special mention must be made of the SuspendedRequested, Suspended , AbortRequested, and Stopped states. When a thread is suspended (via the invocation of its Suspend() method), it does not go into the Suspended state immediately. The .NET runtime will ensure that the thread executes to a point (called the 'safe point') before putting it into the Suspended state. The same thing happens when the Abort() method of a thread is invoked. The runtime ensures that the thread has reached a safe point before putting it into the Stop state.

In other words, suspension and abortion of a thread may not be immediate (and that is why we have the Join() method to deal with threads awaiting abortion).

Like Java, a thread in the Stopped state may not be started again. Attempt- ing to call the Start() method of a stopped thread will throw a ThreadStateException . Once a thread has been stopped, you are holding on to a dead thread which you can't do much with.

17.2.1 How to multi-thread in C#

After all the 'administrative stuff' discussed above, its time to get your hands dirty.

In Java, you can write a thread class in one of two ways “ subclass java.lang. Thread , or write a class which implements the java.lang.Runnable interface. In both cases, you need to write an implementation of the run() method. When the start() method of the thread is called, the run() method will act as the starting point for your thread.

Though the ideas are similar, C# looks at things a bit differently. There is also a System.Threading.Thread class, but this class is sealed (or final, in Java-speak), and there is no interface to implement. What happens is that you create an instance of System.Threading.Thread and pass into it a delegate instance [1] of the StartThread delegate. This StartThread delegate instance encapsulates the method which will act as the starting point for this new thread when its Start() method is invoked.

[1] If you are not sure about C# delegates, just treat it as a class which encapsulates a method for now. A delegate is a new C# type which is used to represent one or more methods . See Chapter 14.

This implies that you can name any method as the starting point when the thread runs. All you have to do is to pass this method name into the StartThread delegate. [2] Unlike C#, Java insists that the starting oint for any thread is the run() method.

[2] Of course, the limitation is that this method designated as the starting point must match the delegate's declaration type. The StartThread delegate will be introduced next .

17.2.2 Instantiating the thread class

Before creating a Thread object, you need to create an instance of the StartThread delegate. The StartThread delegate has this method signature:

 public delegate void StartThread (); 

All methods which are to be compatible with the StartThread delegate must return void , and take in no parameters. This implies that your thread's starting method must return void and take in no parameters too.

Here a StartThread instance is declared by passing in the name of the StartWithMe() method:

 StartThread st = new StartThread(StartWithMe); 

You then create a new Thread object by passing in the delegate instance to its constructor:

 Thread t = new Thread(st); 

So far, what you have done is create a Thread object with a specified method as the starting point. To start the thread, invoke its Start() method:

 t.Start(); 

Here is a full simple example on threading:

 1: using System;  2: using System.Threading;  3:  4: public class TestClass{  5:   public static void PrintA(){  6:     for (int i=0; i<1000; i++)  7:       Console.Write("A");  8:   }  9: 10:   public static void PrintB(){ 11:     for (int i=0; i<1000; i++) 12:       Console.Write("B"); 13:   } 14: 15:   public static void Main(){ 16: 17:     // create new delegate instances 18:     ThreadStart ts1 = new ThreadStart(PrintA); 19:     ThreadStart ts2 = new ThreadStart(PrintB); 20: 21:     // create new thread instances 22:     Thread t1 = new Thread(ts1); 23:     Thread t2 = new Thread(ts2); 24: 25:     // start the threads running 26:     t1.Start(); 27:     t2.Start(); 28:  } 29: } 

Output: [3]

[3] The output has been truncated “ You don't want to see a screen full of A s and B s in this book!

 c:\expt>test BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAA AAAAAAAAAAAAA... 

Two thread instances are created ( t1 and t2 ) on lines 22 “ 23. Their respective entry point methods are PrintA() and PrintB() respectively.

The output shows the two threads running concurrently. For this particular run, the second thread ( ts2 ) started first, and PrintB was invoked to print out a series of B s. Halfway through, the first thread ( ts1 ) kicked in and invoked PrintA to print out a series of A s. After ts1 's time slice expired , ts2 took over again and printed several B s “ and so on.

In this example, both the starting point methods of both threads are static. You can specify an instance method as the starting point of a thread too. If that is the case, you need to create an instance of the class and then pass the reference to the instance method into the delegate's constructor on lines 18 “ 19. Assuming PrintA() and PrintB() are both instance methods of TestClass , you will have to replace lines 16 “ 19 with this code:

 16:   // create new delegate instances 17:   TestClass tc = new TestClass(); 18:   ThreadStart ts1 = new ThreadStart(tc.PrintA); 19:   ThreadStart ts2 = new ThreadStart(tc.PrintB); 

17.2.3 Thread methods

I have introduced the Start() method of Thread . Instead of listing all the methods of Thread , Table 17.2 shows the corresponding methods of java.lang.Thread with System.Threading.Thread along with appropriate comments. More details about each method follow.

After a thread has started running, you can suspend it by calling its Suspend() method. This is Suspend() 's method signature:

 public void Suspend(); 

Calling Suspend() on a thread which is already in the Suspended state has no effect (no exception will be thrown).

You can resume the suspended thread by calling the Resume() method of that thread instance:

 public void Resume(); 

Calling Resume() on a thread which is:

  • not already in the Suspended state, or

  • has been aborted, or

  • has not been started yet

will throw a ThreadStateException .

Table 17.2. Methods of java.lang.Thread and System.Threading.Thread side by side

System.Threading.Thread

java.lang.Thread

Comments

Start()

start()

Starts a thread

Interrupt()

interrupt() [1]

Interrupts a thread in the sleeping/waiting/joining state

Sleep()

sleep()

Puts a thread in the sleeping state “ Java: throws a java.lang.InterruptedException when interrupted . C#: throws a Thread.Threading.ThreadInterruptedException when interrupted

Join()

join()

Puts a thread in the joining state until another thread has completed abortion.

Although both methods allow you to specify a maximum waiting time (so that it returns after timeout even though the other thread is still not completely aborted) there is a difference in their return values.

Java: all overloaded join methods return void . You can't tell if the return is due to a timeout or if the other thread has been successfully aborted.

C#: join returns a boolean “ true will mean that the join returned because the thread has aborted completely; false will mean that join returned because of a timeout

Suspend()

suspend()

suspend() in Java is deprecated because it is deadlock prone

Resume()

resume()

Resumes a thread which has been suspended “ in Java resume() is deprecated

Abort()

stop()

Aborts (or stops or kills ) a running thread. In both Java and .NET, threads which have been aborted cannot start again “ In Java stop() is deprecated

IsBackground (get and set property)

setDaemon(), isDaemon()

Used to alter or get the current thread type. Terminology differences “ a daemon thread in Java is a foreground thread in .NET: a non-daemon thread in Java is a background thread in .NET

IsAlive (get property)

isAlive()

Used to check if a thread is still alive or dead [2]

[1] java.lang.Thread has an isInterrupted() method to check if the thread is currently in the interrupted state. C# has no such equivalent.

[2] Within this context, there is no difference between a non-alive, dead, aborted, or stopped thread and the terms are used interchangeably. They all refer to a thread which is no longer in the active running state, and can never be resurrected again.

You can pause a thread's execution by calling Sleep() . Sleep() takes in an int value which represents the number of milliseconds you want the thread to sleep:

 public static void Sleep(int millisecondsTimeout); 

Unlike Suspend() , Sleep() is a static method and that implies that you cannot invoke the Sleep() method on any particular thread instance. You can only call the static Thread.Sleep() method so that the current thread sleeps. You can suspend another thread by calling the Suspend() method of that thread instance as long as you have a reference variable to it, but you cannot put another thread to sleep “ only the currently running one. Unlike Sleep() , Suspend() has no timeout period.

You can abort a thread by calling Abort() :

 public void Abort(); 

When a thread is aborted, it throws a ThreadAbortException . This is a very special exception in the sense that it cannot be caught using a catch block, but if there is a finally block, that finally block will still execute.

The difference between a suspended thread and an aborted one is that an aborted thread cannot start again. Calling Start() on a thread instance that has been aborted will throw a ThreadStateException . On the other hand, a suspended thread can still be resumed into its active running state by calling its Resume() method. Resume 's method signature looks like this:

 public void Resume(); 

You can check if a thread instance has stopped (aborted) via its IsAlive (get-only) public property. [4] Assuming t is the thread instance, b will be true if t is not in the Stopped state.

[4] If you are not sure what a C# property is, just treat it as a public field for now. Unlike real fields however, properties can be designed so that you can obtain its value only (get property) or assign a value to it only (set property) or both. An example of a get-only property of the Thread class is IsAlive . You can check if the IsAlive property contains true or false (as in bool b = threadInstance.IsAlive ), but you cannot change its value ( threadInstance.IsAlive = false ; causes a compilation error). See Chapter 20.

 bool b = t.IsAlive; 

As previously mentioned, when Abort() or Suspend() of a thread instance has been invoked, the thread may not be aborted or suspended immediately. The scheduler will ensure that execution of the thread continues until a safe point is reached before stopping the thread's line of execution, and putting it into the Stopped state.

Because there is no way to determine when this safe point is reached when aborting a thread, you can use the Join() method so that execution of the calling thread is blocked until the aborted thread has really stopped. Join() is useful if the calling thread's continued execution needs the aborted thread to be completely stopped before carrying on. Join 's method signature looks like this:

 public void Join(); 

Join() is overloaded so that it can take in an int value representing the timeout (in the number of milliseconds). This overloaded method blocks the calling method until either the aborted thread has completely stopped, or if the timeout occurs, whichever happens first. Join 's other method signature looks like this:

 public bool Join (int milliseconds); 

This method returns true if the aborted thread has completely stopped (before the timeout), or false if a timeout has occurred (before the aborted thread has completely stopped).

To get a reference to the currently running thread, use the public property CurrentThread like this:

 Thread current = Thread.CurrentThread; 

This is similar to Java's static Thread.currentThread() method except that in C#'s case, CurrentThread is a property rather than a method.

17.2.4 Thread priorities

As for Java, threads can be assigned priorities in C# too. But unlike Java, which has ten priority levels, C# threads can have one of five:

  • Highest

  • AboveNormal

  • Normal

  • BelowNormal

  • Lowest

In the case of Java, threads spawned off another thread 'inherit' the priority level of the parent thread. In C#, all threads are defaulted to Normal priority regardless of the priority level of the parent thread.

You can set or get a thread instance's priority level via its Priority public property. You can use the ThreadPriority enum to specify the priority level when setting a thread's priority level. The statement below changes the priority level of a thread instance t to Highest .

 t.Priority = ThreadPriority.Highest; 

I have modified the simple threading example in section 17.2.2 by inserting line 25 to make thread ts1 higher in priority than ts2 . Here is the modified program.

 1: using System;  2: using System.Threading;  3:  4: public class TestClass{  5:   public static void PrintA(){  6:     for (int i=0; i<1000; i++)  7:       Console.Write("A");  8:   }  9: 10:   public static void PrintB(){ 11:     for (int i=0; i<1000; i++) 12:       Console.Write("B"); 13:   } 14: 15:   public static void Main(){ 16: 17:     // create new delegate instances 18:     ThreadStart ts1 = new ThreadStart(PrintA); 19:     ThreadStart ts2 = new ThreadStart(PrintB); 20: 21:     // create new thread instances 22:     Thread t1 = new Thread(ts1); 23:     Thread t2 = new Thread(ts2); 24: 25:  t1.Priority  =  ThreadPriority.AboveNormal;  26:     // start the threads running 27:     t1.Start(); 28:     t2.Start(); 29:   } 30: } 

Output: [5]

[5] The output has been truncated. All the A s are displayed before the first B gets printed out.

 c:\expt>test AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAA...BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB... 

Instead of alternating between threads ts1 and ts2 , it is apparent from the output that ts1 executes to completion before ts2 is given any time slice. The output shows all 1000 A s being printed out even before a single B appears.

If there are threads of differing priorities, the general rule is that all the threads of the higher priorities which are in the running state will complete execution first, before the lower-prioritized threads have a chance to run. Be careful when assigning priorities to threads because of the possibility of unintended code starvation . [6]

[6] This is only a general rule. Different operating systems may behave differently concerning thread priorities.

The actual priority level of a thread depends not only on the assigned thread priority in relation to other threads in the same process, but also on the priority of the process the thread is running in compared to other processes. One point to note is that although C# and the BCLs have all these priority mechanisms built in, the C# language specification states that the underlying operating system is not required to honor a thread's priority level.

17.2.5 Foreground versus background threads

Like Java, C# groups threads into two categories “ background and foreground. Foreground threads are like Java daemon threads “ they are the more 'important' ones compared to background threads (or Java non-daemon threads).

A particular process may consist of multiple background and foreground threads running concurrently. If all foreground threads are aborted (stopped), the .NET runtime will automatically abort all other background threads which are still running by invoking their Abort() method.

You can check if a thread in C# is background or foreground via Thread 's IsBackground public property. IsBackground is a getter and setter property “ you can change a background thread to a foreground one (and vice versa) by changing the value of IsBackground directly. By default, all threads are foreground threads.

The program below creates and starts two threads, t1 and t2 . Both are foreground threads by default.

 1: using System;  2: using System.Threading;  3:  4: public class TestClass{  5:  6:   public static void PrintA(){  7:     for (int i=0; i<100; i++)  8:       Console.Write("A");  9:     Console.Write(" Thread 1 complete "); 10:   } 11: 12:   public static void PrintB(){ 13:     while(true) 14:       Console.Write("B"); 15:   } 16: 17:   public static void Main(){ 18:     ThreadStart ts1 = new ThreadStart(PrintA); 19:     ThreadStart ts2 = new ThreadStart(PrintB); 20: 21:     Thread t1 = new Thread(ts1); 22:     Thread t2 = new Thread(ts2); 23: 24:  // t2.IsBackground  =  true;  25:     Console.WriteLine 26:       ("t1 is background: " +t1.IsBackground); 27:     Console.WriteLine 28:       ("t2 is background: " +t2.IsBackground); 29: 30:     t1.Start(); 31:     t2.Start(); 32:   } 33: } 

Output: [7]

[7] The output truncated “ the program continues to print B s until a forced termination occurs.

 c:\expt>test t1 is background: False t2 is background: False AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Thread 1 complete BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB...... 

When ts1 starts, PrintA prints out 100 A s and terminates. When ts2 starts, PrintB goes into an infinite loop, and continues printing B s until you force termination by hitting Ctrl-C .

If line 24 is uncommented, so that t2 becomes a background thread instead of a foreground one, the output changes:

 c:\expt>test t1 is background: False t2 is background: True BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAA Thread 1 complete BBBBBBBBBBBBBBBBBB 

What happens now is that once t1 finishes its for loop and terminates, the runtime realizes that no more foreground thread is running. It will then automatically invoke the Abort() method of all other background threads which are still running. If you examine the new output, you will realize that even after t1 terminates, t2 still runs for a very short while. This may be due to the delay between the termination of t1 and the invocation of t2 's Abort() method, or the fact that the runtime is bringing t2 to a safe point before aborting it completely. Unlike the previous case, t2 no longer carries on running after t1 terminates.



From Java to C#. A Developers Guide
From Java to C#: A Developers Guide
ISBN: 0321136225
EAN: 2147483647
Year: 2003
Pages: 221
Authors: Heng Ngee Mok

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