Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1

.pdf
Скачиваний:
70
Добавлен:
16.08.2013
Размер:
24.18 Mб
Скачать

668 C H A P T E R 1 6 M U L T I T H R E A D E D P R O G R A M M I N G

if (i % 10000000 == 0) Console::WriteLine("Member {0} Thread {1}", name, // Parameter passed

i.ToString());

}

}

void main()

{

Console::WriteLine("Main Program Starts");

// Creating a thread start delegate for a static method

ThreadStart ^thrStart = gcnew ThreadStart(&MyThread::StaticThread); // Use the ThreadStart to create a Thread handle Object

Thread ^thr1 = gcnew Thread(thrStart);

MyThread ^myThr = gcnew MyThread();

//Creating a Thread reference object in one line from a member method Thread ^thr2 = gcnew Thread(

gcnew ParameterizedThreadStart(myThr, &MyThread::NonStaticThread));

//Uncomment for background vs foreground exploration

//thr1->IsBackground = true;

//thr2->IsBackground = true;

//Actually starting the threads

thr1->Start(); thr2->Start("Parameterized");

Console::WriteLine("Main Program Ends");

}

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

gcnew ThreadStart(MyThread::StaticThread)

gcnew ThreadStart(myThr, &MyThread::MemberThread)

gcnew ParameterizedThreadStart(MyThread::StaticThread)

gcnew ParameterizedThreadStart(myThr, &MyThread::MemberThread)

The first parameter is a handle to the class that contains the delegate method. For a static method, there is no class handle, so the first parameter is not passed. The second parameter is a fully qualified method.

The second 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 the 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 no error results, you should get something like Figure 16-2.

C H A P T E R 1 6 M U L T I T H R E A D E D P R O G R A M M I N G

669

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

thr1->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 you might not expect, though, is 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.

670 C H A P T E R 1 6 M U L T I T H R E A D E D P R O G R A M M I N G

Listing 16-2. Making a Thread Sleep

using namespace System;

using namespace System::Threading;

ref class MyThread

{

public:

static void ThreadFunc(Object ^Name);

};

void MyThread::ThreadFunc(Object ^Name)

{

for (int i = 0; i < 101; i++)

{

if (i % 10 == 0)

Console::WriteLine("{0} {1}", Name, i.ToString()); Thread::Sleep(10);

}

}

void main()

{

Console::WriteLine("Main Program Starts");

Thread ^thr1 =

gcnew Thread(gcnew ParameterizedThreadStart(&MyThread::ThreadFunc)); Thread ^thr2 =

gcnew Thread(gcnew ParameterizedThreadStart(&MyThread::ThreadFunc));

thr1->Start("Thread1"); thr2->Start("Thread2");

int iHour = 0; int iMin = 0; int iSec = 1;

Thread::Sleep(TimeSpan(iHour, iMin, iSec));

Console::WriteLine("Main Program Ends");

}

Listing 16-2 has a couple of additional bits of bonus logic. First, it shows how to get a handle 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 = "Thread1";

//Then later in thread itself

String ^threadName = Thread::CurrentThread->Name;

C H A P T E R 1 6 M U L T I T H R E A D E D P R O G R A M M I N G

671

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

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 is 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.

672 C H A P T E R 1 6 M U L T I T H R E A D E D P R O G R A M M I N G

Listing 16-3. Aborting a Thread

using namespace System;

using namespace System::Threading;

ref class MyThread

{

public:

static void ThreadFunc(Object ^Name);

};

void MyThread::ThreadFunc(Object ^Name)

{

Thread ^thr = Thread::CurrentThread; try

{

for (int i = 0; i < 100; i++)

{

Console::WriteLine("{0} {1}", Name, i.ToString()); Thread::Sleep(1);

}

return;

}

catch (ThreadAbortException^)

{

Console::WriteLine("{0} Aborted", Name);

//Reset the abort so that the method will continue processing

//thr->ResetAbort();

}

}

void main()

{

Console::WriteLine("Main Program Starts");

Thread ^thr1 =

gcnew Thread(gcnew ParameterizedThreadStart(&MyThread::ThreadFunc)); Thread ^thr2 =

gcnew Thread(gcnew ParameterizedThreadStart(&MyThread::ThreadFunc));

thr1->Start("Thread1"); thr2->Start("Thread2");

Thread::Sleep(20); thr1->Abort(); Thread::Sleep(40); thr2->Abort();

C H A P T E R 1 6 M U L T I T H R E A D E D P R O G R A M M I N G

673

try

{

thr1->Start();

}

catch (ThreadStateException ^tse)

{

Console::WriteLine(tse->ToString());

}

Console::WriteLine("Main Program Ends");

}

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.

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.

674 C H A P T E R 1 6 M U L T I T H R E A D E D P R O G R A M M I N G

Listing 16-4. Joining Threads

using namespace System;

using namespace System::Threading;

ref class MyThread

{

public:

static void ThreadFunc(Object ^Name);

};

void MyThread::ThreadFunc(Object ^Name)

{

for (int i = 0; i < 5; i++)

{

Console::WriteLine("{0} {1}", Name, i.ToString()); Thread::Sleep(1);

}

}

void main()

{

Console::WriteLine("Before starting thread");

Thread ^thr1 =

gcnew Thread(gcnew ParameterizedThreadStart(&MyThread::ThreadFunc)); Thread ^thr2 =

gcnew Thread(gcnew ParameterizedThreadStart(&MyThread::ThreadFunc));

thr1->Start("Thread1"); thr1->Join();

thr2->Start("Thread2");

Console::WriteLine("End of Main");

}

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.

Figure 16-5. The JoiningThreads program in action

C H A P T E R 1 6 M U L T I T H R E A D E D P R O G R A M M I N G

675

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 deadlocking or starvation as the suspended thread might hold resources needed by other threads.

For example, the worker thread from the preceding example may hold a lock on a database that the drawing thread uses to draw the display. Since the worker thread is suspended, it will never relinquish its hold on the database, and the drawing thread will wait forever for the hold on the database to be released.

676 C H A P T E R 1 6 M U L T I T H R E A D E D P R O G R A M M I N G

Note The Suspend() and Resume() methods have been marked as obsolete in version 2.0 of the .NET Framework and will probably disappear in future releases. The reason is they are so deadlock-prone that using them in all but the simplest cases is problematic. Microsoft suggests using the Monitor, Mutex, Event, or Semaphore instead, which I cover later in the chapter (except for Event, as I covered that way back in Chapter 4). I am leaving this section (from the previous version of the book) in the book for those of you who have used these methods in the past, but I suggest that you refrain from implementing anything new using them.

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

using namespace System;

using namespace System::Threading;

ref class MyThread

{

public:

static void ThreadFunc1(); static void ThreadFunc2();

};

void MyThread::ThreadFunc1()

{

Console::WriteLine("Before long sleep"); try

{

Thread::Sleep(Timeout::Infinite);

}

catch(ThreadInterruptedException^){/*continue processing*/} Console::WriteLine("After long sleep");

}

void MyThread::ThreadFunc2()

{

for (int i = 0; i < 5; i++)

{

Console::WriteLine("Thread {0}",i.ToString()); Thread::Sleep(2);

}

}

void main()

{

Thread ^thr1 = gcnew Thread(gcnew ThreadStart(&MyThread::ThreadFunc1)); Thread ^thr2 = gcnew Thread(gcnew ThreadStart(&MyThread::ThreadFunc2));

Console::WriteLine("Sleep/interrupt thread"); thr1->Start();

C H A P T E R 1 6 M U L T I T H R E A D E D P R O G R A M M I N G

677

Thread::Sleep(4);

for (int i = 0; i < 4; i++)

{

Console::WriteLine("**Main2 {0}", i.ToString()); Thread::Sleep(2);

}

thr1->Interrupt(); thr1->Join();

Console::WriteLine("\nSuspend/resume thread"); thr2->Start();

Thread::Sleep(8); thr2->Suspend();

for (int i = 0; i < 4; i++)

{

Console::WriteLine("**Main1 {0}", i.ToString()); Thread::Sleep(2);

}

thr2->Resume();

}

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

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.

Using ThreadPools

As the name of the class suggests, System::Threading::ThreadPool provides a system-managed pool of threads on which to run your application’s threads. Being managed by the system, your multithreaded application loses control of how threads are created, managed, and cleaned up. But, in many cases, your application has no real need to manage threads, as aborting, joining, interrupting, suspending, and resuming a thread in an application is not always needed.