The Thread Class


With the Thread class you can create and control threads. The code here is a very simple example of creating and starting a new thread. The constructor of the Thread class accepts a delegate parameter of type ThreadStart and ParameterizedThreadStart. The ThreadStart delegate defines a method with a void return type and without arguments. After the Thread object is created, you can start the thread with the Start() method:

  using System; using System.Threading; namespace Wrox.ProCSharp.Threading {    class Program    {       static void Main()       {          Thread t1 = new Thread(ThreadMain);          t1.Start();          Console.WriteLine("This is the main thread.");       }       static void ThreadMain()       {          Console.WriteLine("Running in a thread.");       }    } } 

Running the application, you get the output of the two threads:

 This is the main thread. Running in a thread.

There’s no guarantee what output comes first. Threads are scheduled by the operating system; which thread comes first can be different each time.

You’ve seen how an anonymous method can be used with an asynchronous delegate. You can use it with the Thread class as well passing the implementation of the thread method to the argument of the Thread constructor:

 using System; using System.Threading; namespace Wrox.ProCSharp.Threading {

    class Program    {       static void Main()       {          Thread t1 = new Thread(                delegate()                {                   Console.WriteLine("running in a thread");                });          t1.Start();          Console.WriteLine("This is the main thread.");       }    } }

If you don’t need a variable referencing the thread to control the thread after it was created, you can also write the same in a shorter way. Create a new Thread object with the constructor, pass an anonymous method to the constructor, and with the Thread object returned invoke the Start() method directly:

 using System.Threading; namespace Wrox.ProCSharp.Threading {    class Program    {       static void Main()       {          new Thread(                delegate()                {                   Console.WriteLine("running in a thread");                }).Start();          Console.WriteLine("This is the main thread.");       }    } }

However, there are some good reasons having a variable to reference the Thread object. One example, for a better control of the threads, you can assign a name to the thread by setting the Name property before starting the thread. To get the name of the current thread, you can use the static property Thread .CurrentThread to get to the Thread instance of the current thread and access the Name property for read access. The thread also has a managed thread ID that you can read with the property ManagedThreadId.

 static void Main() {    Thread t1 = new Thread(ThreadMain);    t1.Name = "MyNewThread1";    t1.Start();    Console.WriteLine("This is the main thread."); } static void ThreadMain() {    Console.WriteLine("Running in the thread {0}, id: {1}.",          Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId); }

With the output of the application now you can also see the thread name and ID:

 This is the main thread. Running in the thread MyNewThread1, id: 3.

Important 

Assigning a name to the thread helps a lot with debugging threads. During your debugging session with Visual Studio, you can turn on the Debug Location toolbar that shows the name of the thread.

Passing Data to Threads

If you need to pass some data to a thread, you can do this in two ways. You can either use the Thread constructor with the ParameterizedThreadStart delegate, or you can create a custom class and define the method of the thread as an instance method, so you can initialize data of the instance before starting the thread.

For passing data to a thread, any class or struct that holds the data is needed. Here the struct Data containing a string is defined, but you can pass any object you want.

  public struct Data {    public string Message; } 

If the ParameterizedThreadStart delegate is used, the entry point of the thread must have a parameter of type object and a void return type. The object can be cast to what it is, and here the message is written to the console.

  static void ThreadMainWithParameters(object o) {    Data d = (Data)o;    Console.WriteLine("Running in a thread, received {0}", d.Message); } 

With the constructor of the Thread class, you can assign the new entry point ThreadMainWithParameters and invoke the Start() method passing the variable d.

  static void Main() {    Data d = new Data();    d.Message = "Info";    Thread t2 = new Thread(ThreadMainWithParameters);    t2.Start(d); } 

Another way to pass data to the new thread is to define a class (see the class MyThread), where you define the fields that are needed as well as the main method of the thread as an instance method of the class:

  public class MyThread {    private string data;    public MyThread(string data)    {       this.data = data;    }    public void ThreadMain()    {       Console.WriteLine("Running in a thread, data: {0}", data);    } } 

This way you can create an object of MyThead, and pass the object and the method ThreadMain() to the constructor of the Thread class. The thread can access the data.

  MyThread obj = new MyThread("info"); Thread t3 = new Thread(obj.ThreadMain); t3.Start(); 

Background Threads

The process of the application keeps running as long as at least one foreground thread is running. If more than one foreground thread is running and the Main method ends, the process of the application keeps active until all foreground threads finish their work.

A thread you create with the Thread class, by default, is a foreground thread. Thread pool threads are always background threads.

When you create a thread with the Thread class, you can define if it should be a foreground or background thread by setting the property IsBackground. The Main() method sets the IsBackground property of the thread t1 to false (which is the default). After starting the new thread, the main thread just writes to the console an end message. The new thread writes a start and an end message, and in between it sleeps for 3 seconds. The 3 seconds provide a good chance that the main thread is finished before the new thread completes its work.

  class Program  {     static void Main()     {        Thread t1 = new Thread(ThreadMain);        t1.Name = "MyNewThread1";        t1.IsBackground = false;        t1.Start();        Console.WriteLine("Main thread ending now...");     }     static void ThreadMain()     {        Console.WriteLine("Thread {0} started", Thread.CurrentThread.Name);        Thread.Sleep(3000);        Console.WriteLine("Thread {0} completed", Thread.CurrentThread.Name);     }  } 

When you start the application, you will still see the completion message written to the console although the main thread completed its work earlier. The reason is that the new thread is a foreground thread as well.

 Main thread ending now... Thread MyNewThread1 started Thread MyNewThread1 completed

If you change the IsBackground property to start the new thread to true, the result shown at the console is different. On my system, I see the start message of the new thread but never the end message. It might happen that you don’t see the start message as well when the thread was prematurely ended before it had a chance to kick off.

 Main thread ending now... Thread MyNewThread1 started

Background threads are very useful for background tasks. For example, if you close the Word application, it wouldn’t make sense for the spell checker to keep its process running. The spell checker thread can be killed when the application is closed. However, the thread organizing the Outlook message store should remain active until it is finished even if Outlook is closed.

Thread Priority

You’ve learned that the operating system schedules threads. You’ve had a chance to influence the scheduling by assigning a priority to the thread.

Before changing the priority, you must understand the thread scheduler. The operating system schedules threads based on a priority. The thread with the highest priority is scheduled to run in the CPU. A thread stops running and gives up the CPU if it waits for a resource. There are several reasons that a thread must wait, for example in response to a sleep instruction, while waiting for disk I/O to complete, while waiting for a network packet to arrive, and so on. If the thread does not give up the CPU on its own, it is preempted by the thread scheduler. If a thread does have a time quantum, it can use the CPU continuously. If there are multiple threads running with the same priority waiting to get the CPU, the thread scheduler uses a round-robin scheduling principle to give the CPU to one thread after the other. If a thread is preempted, it goes last to the queue.

The time quantum and round-robin principles are used only if multiple threads are running at the same priority. The priority is dynamic. If a thread is CPU-intensive (requires the CPU continuously without waiting for resources), the priority is lowered to the level of the base priority that is defined with the thread. If a thread is waiting for a resource, the thread gets a priority boost and the priority is increased. Because of the boost, there’s a good chance that the thread gets the CPU the next time that the wait ends.

With the Thread class, you can influence the base priority of the thread by setting the Priority property. The Priority property requires a value that is defined by the ThreadPriority enumeration. The levels defined are Highest, AboveNormal, Normal, BelowNormal, and Lowest. Be careful when giving a thread a higher priority, as this may decrease the chance for other threads to run. You can change the priority for a short time if needed.

Controlling Threads

The thread is created by invoking the Start() method of a Thread object. However, after invoking the Start() method, the new thread is still not in the Running state, but in the Unstarted state instead. The thread changes to the Running state as soon as the operating system thread scheduler selects the thread to run. You can read the current state of a thread by reading the property Thread.ThreadState.

With the Thread.Sleep() method a thread goes into the WaitSleepJoin state and waits until it is woken up again after the timespan defined with the Sleep() method has elapsed.

To stop another thread, you can invoke the method Thread.Abort(). When this method is called, an exception of type ThreadAbortException is thrown in the thread that receives the abort. With a handler to catch this exception, the thread can do some cleanup before it ends. The thread also has a chance to continue running after receiving the ThreadAbortException as a result of invoking Thread.ResetAbort(). The state of the thread receiving the abort request changes from AbortRequested to the Aborted state if the thread does not reset the abort.

If you need to wait for a thread to end, you can invoke the Thread.Join() method. Thread.Join() blocks the current thread and sets it to the WaitSleepJoin state until the thread that is joined is completed.

.NET 1.0 also supported Thread.Suspend() and Thread.Resume() methods to pause and continue a thread, respectively. However, you don’t know what the thread is doing when it gets the Suspend request, and the thread might be in a synchronized section holding locks. This can easily result in deadlocks. That’s why these methods are now obsolete. Instead, you can signal a thread, using synchronization objects, so it can suspend itself. This way, the thread knows best when to go into a waiting state.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

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