Using Threads


Of the two methods available in the .NET Framework for creating threads, Thread and ThreadPool, the System::Threading::Thread class provides you with the most control and versatility. The cost is a minor amount of additional coding complexity.

Like all classes in the .NET Framework, the Thread class is made up of properties and methods. The ones you will most likely use are as follows:

  • Abort() is a method that raises a ThreadAbortException in the thread on which it is invoked, which starts the process of terminating the thread. Calling this method normally results in the termination of the thread.

  • CurrentThread is a static Thread property that represents the currently running thread.

  • Interrupt() is a method that interrupts a thread that is currently in the WaitSleepJoin thread state, thus resulting in the thread returning to the Running thread state.

  • IsBackground is a Boolean property that represents whether a thread is a background or a foreground thread. The default is false.

  • Join() is a method that causes the calling thread to block until the called thread terminates.

  • Name is a String property that represents the name of the thread. You can write the name only once to this property.

  • Priority is a ThreadPriority enumerator property that represents the current priority of the thread. The default is Normal.

  • Resume() is a method that resumes a suspended thread and makes its thread state Running.

  • Sleep() is a method that blocks the current thread for a specified length of time and makes its thread state WaitSleepJoin.

  • Start() is a method that causes the thread to start executing and changes its thread state to Running.

  • Suspend() is a method that causes the thread to suspend. The thread state becomes Suspended.

  • ThreadState is a ThreadState enumerator property that represents the current thread state of the thread.

The idea of running and keeping track of two or more things at the same time can get confusing. Fortunately, in many cases with multithreaded programming, you simply have to start a thread and let it run to completion without interference.

I start off by showing you that exact scenario first. Then I show you some of the other options available to you when it comes to thread control.

Starting Threads

The first thing that you need to do to get the multithreaded programming running is to create an instance of a Thread. You don't have much in the way of options, as there is only one constructor:

 System::Threading::Thread ( System::Threading::ThreadStart *start ); 

The parameter ThreadStart is a delegate to the method that is the starting point of the thread. The signature of the delegate is a method with no parameters that returns void:

 public __gc __delegate void ThreadStart(); 

One thing that may not be obvious when you first start working with threads is that creating an instance of the Thread object doesn't cause the thread to start. The thread state after creating an instance of the thread is, instead, Unstarted. To get the thread to start, you need to call the Thread class's Start() method. It kind of makes sense, don't you think?

I think it's about time to look at some code. Take a look at the example of a program that creates two threads in Listing 16-1. The first thread executes a static method of a class and the second thread executes a member class.

Listing 16-1: Starting Two Simple Threads

start example
 using namespace System; using namespace System::Threading; __gc class MyThread { public:     static void StaticThread()     {         for (Int32 i = 0; i < 5000001; i++)         {             if (i % 1000000 == 0)                 Console::WriteLine(S"Static Thread {0}", i.ToString());         }     }     void NonStaticThread()     {          for (Int32 i = 0; i < 5000001; i++)          {              if (i % 1000000 == 0)                  Console::WriteLine(S"Member Thread {0}", i.ToString());          }     } }; Int32 main() {     Console::WriteLine(S"Main Program Starts");     // Creating a thread start delegate for a static method     ThreadStart *thrStart = new ThreadStart(0, &MyThread::StaticThread);     // Use the ThreadStart object to create a Thread pointer Object     Thread *tid1 = new Thread(thrStart);     MyThread *myThr = new MyThread();     // Creating a Thread reference object in one line from a member method     Thread &tid2 =         *new Thread(new ThreadStart(myThr, &MyThread::NonStaticThread)); //    Uncomment for background vs foreground exploration //    thr1->IsBackground = true; //    thr2.IsBackground = true;     // Actually starting the pointer and reference threads     tid1->Start();     tid2.Start();     Console::WriteLine(S"Main Program Ends");     return 0; } 
end example

There are some things you might want to notice about Listing 16-1. First, Threads can be pointers or references, as you should have expected, because they are classes like any other.

The second thing of note is the subtle difference between creating an instance of a delegate from a static method and creating an instance of a delegate from a member method:

 new ThreadStart(0, MyThread::StaticThread) new ThreadStart(myThr, &MyThread::MemberThread) 

The first parameter is a pointer to the class that contains the delegate method. For a static method there is no class pointer, so the first parameter is set to null or 0. The second parameter is a pointer to the fully qualified method.

The third thing of note is that I had to use really big loops for this example to show the threading in process. For smaller loops, the first thread finished before second thread even started. (Wow, computers are fast!)

Okay, execute StartingThreads.exe by pressing Ctrl-F5. This will compile the program and start it without the debugger. If you have no error, you should get something like Figure 16-2.

click to expand
Figure 16-2: The StartingThreads program in action

Take a look at the top of your output. Your main program started and ended before the threads even executed their first loop. As you can see, foreground threads (which these are) continue to run even after the main thread ends.

If you were to uncomment these two lines, before the start method calls, with the lines

      thrl->IsBackground = true;      thr2.IsBackground = true; 

then you would find that the threads stop abruptly without completing when the main thread ends, just as you would expect. Something I didn't expect, though, was that if you set only one of the threads to the background, it doesn't end when the main thread ends but instead continues until the second "foreground" thread completes.

Getting a Thread to Sleep

When you develop your thread, you may find that you don't need it to continually run or you might want to delay the thread while some other thread runs. To handle this, you could place a delay loop like a "do nothing" for loop. However, doing this wastes CPU cycles. What you should do instead is temporarily stop the thread or put it to sleep.

Doing this couldn't be easier. Simply add the following static Thread method:

    Thread::Sleep(timeToSleepInMilliseconds); 

This line causes the current thread to go to sleep for the interval specified either in milliseconds or using the TimeSpan structure. The TimeSpan structure specifies a time interval and is created using multiple overloaded constructors:

 TimeSpan(Int64 ticks); TimeSpan(Int32 hours,Int32 minutes,Int32 seconds); TimeSpan(Int32 days,Int32 hours,Int32 minutes,Int32 seconds); TimeSpan(Int32 days,Int32 hours,Int32 minutes,Int32 seconds,Int32 milliseconds); 

The Sleep() method also takes two special values: Infinite, which means sleep forever, and 0, which means give up the rest of the thread's current CPU time slice.

A neat thing to notice is that main() and WinMain() are also threads. This means you can use Thread::Sleep() to make any application sleep. In Listing 16-2, both worker threads and the main thread are all put to sleep temporarily.

Listing 16-2: Making a Thread Sleep

start example
 using namespace System; using namespace System::Threading; __gc class MyThread { public:     static void ThreadFunc()     {          Thread *thr = Thread::CurrentThread;          for (Int32 i = 0; i < 101; i++)          {               if (i % 10 == 0)                   Console::WriteLine(S"{0} {1}", thr->Name, i.ToString());               Thread::Sleep(10); // sleep 10 milliseconds          }     } }; Int32 main() {     Console::WriteLine(S"Main Program Starts");     MyThread *myThr1 = new MyThread();     Thread &thr1 = *new Thread(new ThreadStart(myThr1, &MyThread::ThreadFunc));     Thread &thr2 = *new Thread(new ThreadStart(myThr1, &MyThread::ThreadFunc));     thr1.Name = S"Thread1";     thr2.Name = S"Thread2";     thr1.Start();     thr2.Start();     Int32 iHour = 0;     Int32 iMin = 0;     Int32 iSec = 1;     Thread::Sleep(TimeSpan(iHour, iMin, iSec)); // sleep one second     Console::WriteLine(S"Main Program Ends");     return 0; } 
end example

Listing 16-2 has a couple of additional bits of bonus logic. First, it shows how to get a pointer to the current thread using the Thread class's CurrentThread property:

         Thread *thr = Thread::CurrentThread; 

Second, it shows how to assign a name to a thread using the Thread class's Name property, which you can retrieve later within the thread:

 // when creating thread add thr1.Name = S"Thread1"; // then later in thread itself String *threadName = Thread::CurrentThread->Name; 

Tip

Though it isn't the purpose of the thread's Name property, you could use the Name property to pass a parameter (in String format) to a thread, instead of (or as well as) the name of the thread.

The results of SleepingThreads.exe are shown in Figure 16-3.

click to expand
Figure 16-3: The SleepingThreads program in action

Notice that the main thread ends in the middle of the thread execution, instead of before it starts, like in the previous example. The reason being the main thread is put to sleep while the worker threads run and then it wakes up just before the other threads end.

Aborting Threads

You might, on occasion, require that a thread be terminated within another thread before it runs through to its normal end. In such a case, you would call the Abort() method. This method will, normally, permanently stop the execution of a specified thread.

Notice that I used the term "normally." What actually happens when a thread is requested to stop with the Abort() method is that a ThreadAbortException exception is thrown within the thread. This exception, like any other, can be caught but, unlike most other exceptions, ThreadAbortException is special as it gets rethrown at the end of the catch block unless the aborting thread's ResetAbort() method is called. Calling the ResetAbort() method cancels the abort, which in turn prevents ThreadAbortException from stopping the thread.

Caution

Something that you must be aware of is that an aborted thread can't be restarted. If you attempt to do so, a ThreadStateException exception is thrown instead.

Listing 16-3 shows the Abort() method in action. First it creates two threads, and then it aborts them. Just for grins and giggles, I then try to restart an aborted thread, which promptly throws an exception.

Listing 16-3: Aborting a Thread

start example
 using namespace System; using namespace System::Threading; __gc class MyThread { public:     static void ThreadFunc()     {         Thread *thr = Thread::CurrentThread;         try         {             for (Int32 i = 0; i < 100; i++)             {                 Console::WriteLine(S"{0} {l}", thr->Name, i.ToString());                 Thread::Sleep(1);             }         }         catch (ThreadAbortException*)         {             Console::WriteLine(S"{0} Aborted", thr->Name);             // Reset the abort so that the method will continue processing             // thr->ResetAbort();         }     } }; Int32 main() {     Console::WriteLine(S"Main Program Starts");     Thread &thr1 = *new Thread(new ThreadStart(0, &MyThread::ThreadFunc));     Thread &thr2 = *new Thread(new ThreadStart(0, &MyThread::ThreadFunc));     thr1.Name = S"Thread1";     thr2.Name = S"Thread2";     thr1.Start();     thr2.Start();     Thread::Sleep(6);     thr1.Abort();     Thread::Sleep(6);     thr2.Abort();     try     {         thr1.Start();     }     catch (ThreadStateException *tse)     {         Console::WriteLine(tse->ToString());     }     Console::WriteLine(S"Main Program Ends");     return 0; } 
end example

In the exception of the Thread method, I've added (but commented out) the code required to reset the abort so that the thread continues instead of ending.

Figure 16-4 shows AbortingThreads.exe in action. As you can see, even though I catch the ThreadAbortException exception in the thread, the thread still aborts after leaving the catch block. As expected, when I try to restart a thread, a ThreadStateException exception is thrown.

click to expand
Figure 16-4: The AbortingThreads program in action

Joining Threads

Back in the first example in this chapter, you saw that after you created your threads and started them, the main program then proceeded to terminate. In the case of the first example this is fine, but what if you want to execute something after the threads finish? Or, more generally, how do you handle the scenario where one thread needs to wait for another thread to complete before continuing?

What you need to do is join the threads using the Thread class's Join() method. You can join threads in three different ways by using one of the three overloaded Join() methods. The first overloaded method takes no parameters and waits until the thread completes, and the second takes an int parameter and then waits the parameter's specified number of milliseconds or for the thread to terminate, whichever is shorter. The third overload takes a TimeSpan struct and functions the same as the previous overload.

The simple example in Listing 16-4 joins the main thread to the first worker thread and then waits for the worker thread to complete before starting the second worker thread.

Listing 16-4: Joining Threads

start example
 using namespace System; using namespace System::Threading; __gc class MyThread { public:     static void ThreadFunc()     {         Thread *thr = Thread::CurrentThread;         for (Int32 i = 0; i < 5; i++)         {             Console::WriteLine(S"{0} {1}", thr->Name, i.ToString());             Thread::Sleep(1);         }     } }; Int32 main() {     Console::WriteLine(S"Before starting thread");     Thread &thr1 = *new Thread(new ThreadStart(0, &MyThread::ThreadFunc));     Thread &thr2 = *new Thread(new ThreadStart(0, &MyThread::ThreadFunc));     thr1.Name = S"Thread1";     thr2.Name = S"Thread2";     thr1.Start();     thr1.Join();     thr2.Start();     Console::WriteLine("End of Main"); } 
end example

Figure 16-5 shows JoiningThreads.exe in action. Notice that the main thread terminates again after both threads are started, but this time the main thread waited for the first worker thread to end before starting the second thread.

click to expand
Figure 16-5: The JoiningThreads program in action

Interrupting, Suspending, and Resuming Threads

It is completely possible to take a worker thread and place it in a tight loop, waiting for some event to occur. Doing this would be a big waste of CPU cycles. It would be better to let the worker thread sleep and then be woken up when the event occurs. You can do exactly that using a combination of Sleep() and Interrupt() methods, in conjunction with the System::Threaded::ThreadInterruptedException exception.

The basic idea is to put the worker thread to sleep using the static Sleep() method, and then interrupt (the sleep of) the worker thread when the required event occurs using the Interrupt() member method. Simple enough, I think, except that the Interrupt() method throws a ThreadInterruptedException exception instead of just terminating the Sleep() method. Thus, you need to place the Sleep() method in the try of a try/catch block, and then have the worker thread continue execution in the catch.

Here's the worker thread:

 try {       // Wait for event to occur       Thread::Sleep(Timeout::Infinite); } catch(ThreadInterruptedException*) {       /*continue processing*/ } 

Here's some other thread:

 WorkerThread->Interrupt(); 

The preceding scenario will work if the worker thread knows when to go to sleep. It may also be necessary to allow another thread to temporarily stop a different thread and then restart it again later.

For example, a worker thread could be doing some intense number crunching when along comes another thread that needs to put a large graphic up on the monitor as soon as possible (the user interface should almost always get priority).

You can resolve this scenario in at least three ways. First, you could do nothing special and let the multithreading engine slowly display the graphic. Second, you could raise the priority of the graphic display thread (or lower the priority of the worker thread), thus giving the graphic display more cycles. Or third, you could suspend the worker thread, then draw the graphic and, finally, resume the worker thread. Doing it this way requires two methods and would be done like this:

 WorkerThread->Suspend(); // do stuff WorkerThread->Resume(); 

Caution

Choosing either the second or third methods mentioned previously can have some negative side effects. Changing priorities could lead to sluggish interface response time because the interface thread is now a lower priority. Suspending a thread could lead to thread starvation as the suspended thread might hold resources needed by other threads.

Listing 16-5 shows how to implement both of the Thread class's sleep/interrupt and suspend/resume functionalities.

Listing 16-5: Sleeping/Interrupting and Suspending/Resuming a Thread

start example
 using namespace System; using namespace System::Threading; __gc class MyThread { public:     static void ThreadFunc1()     {         Console::WriteLine(S"Before long sleep");         try         {             Thread::Sleep(Timeout::Infinite);         }         catch(ThreadInterruptedException*){/*continue processing*/}         Console::WriteLine(S"After long sleep");     }     static void ThreadFunc2()     {         for (Int32 i = 0; i < 5; i++)         {             Console::WriteLine(S"Thread {0}",i.ToString());             Thread::Sleep(2);         }     } }; Int32 main() {     Thread &thr1 = *new Thread(new ThreadStart(0, &MyThread::ThreadFunc1));     Thread &thr2 = *new Thread(new ThreadStart(0, &MyThread::ThreadFunc2));     Console::WriteLine(S"Sleep/interrupt thread");     thr1.Start();     Thread::Sleep(4);     for (Int32 i = 0; i < 4; i++)     {           Console::WriteLine(S"**Main2 {0}", i.ToString());           Thread::Sleep(2);     }     thr1.Interrupt();     thr1.Join();     Console::WriteLine(S"\nSuspend/resume thread");     thr2.Start();     Thread::Sleep(8);     thr2.Suspend();     for (Int32 i = 0; i < 4; i++)     {         Console::WriteLine(S"**Main1 {0}", i.ToString());         Thread::Sleep(2);     }     thr2.Resume();     return 0; } 
end example

You can see the results of ISRingThreads.exe in Figure 16-6.

click to expand
Figure 16-6: The ISRingThreads program in action

Notice how both provide a similar flow through their threads. The major difference between sleep/interrupt and suspend/resume is which thread initiates the temporary stopping of the worker thread.




Managed C++ and. NET Development
Managed C++ and .NET Development: Visual Studio .NET 2003 Edition
ISBN: 1590590333
EAN: 2147483647
Year: 2005
Pages: 169

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