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

Microsoft Visual C++ .NET Professional Projects - Premier Press

.pdf
Скачиваний:
168
Добавлен:
24.05.2014
Размер:
25.78 Mб
Скачать

140

Part I

INTRODUCING VC++.NET

 

 

 

When the worker thread is created, it executes independent of the other threads of the application. As discussed before, the worker thread cannot request for the resources, and hence utilizes the resources allocated to the process to which it belongs.

The AfxBeginThread Function for User-Interface Threads

its primary thread. However, you can create more than one user-interface thread,

In most cases, an application consists of onlyYone user-interface thread, which is

if required.

L F Similar to worker threads, to createMa user-interface thread, you need to derive a

derived class by using the DECL RE DYNCREATE and IMPLEMENT_DYNCREATE macros

class from the CWinThread class. However, just deriving a class from the CWinThread class is not sufficient.AYou must also declare and implement the

to

enable the dynamic creation and implementation of the class at run time.

 

Besides

E

you

also

 

The InitInstanceTfunction that you override to initialize an instance of the thread. You must override this function to create a thread.

The Run function that you override to control the execution of a thread. This function is rarely overridden.

The OnIdle function that you override to perform the thread-specific idle-time processing. This function is also rarely overridden.

The ExitInstance function that you override to perform the cleanup task when the thread terminates. Overriding this function is also optional.

Next, you need to use one of the overloaded AfxBeginThread functions to create a user-interface thread. The syntax for the AfxBeginThread function to be used is as follows:

CWinThread* AfxBeginThread(CRuntimeClass* ThreadClass, int ThreadPriority = THREAD_PRIORITY_NORMAL, UINT

StackSize = 0, DWORD

ControlFlags = 0,

LPSECURITY_ATTRIBUTES Security = NULL);

Team-Fly®

THREADS

Chapter 6

 

141

 

 

 

 

 

This version of the AfxBeginThread function accepts the same set of parameters as the version used to create worker threads, other than the first two parameters. The first parameter for user threads accepts the name of the RUNTIME_CLASS object of the CWinThread derived class. This version of the AfxBeginThread function does not accept any parameters for the thread-handling function. When you execute the application, you need to call the AfxBeginThread function to start the user-interface thread. This function dynamically creates an object of the CWinThread derived class, initializes the object, and starts the thread execution.

Creating a Single-Threaded Application

As discussed earlier, every application starts with a single thread. This section discusses an application that simulates the car-racing game. In this application, when you click on the application window, rectangles start moving from the left side of the application window until they reach the right side.

To create a single-threaded application, create an SDI application, Race, and make the following changes to the RaceView class in the RaceView.h file:

1. Add a function to start the race, as follows:

void race(int no, COLORREF col);

2. Type the following code for the race function in the RaceView.cpp file:

void CRaceView::race(int no, COLORREF col)

{

//Create a pointer to the client area of the

//application window CClientDC *dc;

dc=new CClientDC(this);

//Create a brush with color passed as a parameter CBrush br(col);

//Apply the brush to the device context dc->SelectObject(br);

//Create and initialize a variable to control the x //coordinate of rectangles

int x=10;

//Create and initialize a variable to control the y //coordinate of each rectangle

//Multiply the rectangle number by 60 to change the y

142

Part I

INTRODUCING VC++.NET

 

 

 

//coordinate of each rectangle int y=no*60;

// Loop for creating and moving the rectangle while(x<690)

{

// Display a rectangle dc->Rectangle(x,y,x+25,y+40);

// A dummy loop for swift movement of the // rectangle

for(int j=0;j<=1000000;j++);

// Clear the rectangle before creating the //next rectangle

// to display the movement dc->SetROP2(R2_NOTXORPEN); dc->Rectangle(x,y,x+25,y+40); x++;

}

//Display the final location of the rectangle dc->Rectangle(x,y,x+25,y+40);

}

The preceding code defines the race function of the RaceView class. The function takes two parameters of the int and COLORREF type. The int parameter is used to trap the number of the racing rectangle, and COLORREF defines its color. In the function, a pointer to the client area of the application window is created. In addition to the pointer, an object of CBrush type is created, which you use to apply the color, passed as a parameter, to the objects in the client area of the window. The while loop creates and moves the rectangle by using the Rectangle function of the CClientDC class.

Before moving to the next point, the SetROP2 function sets the pixel color that is not available in the Pen or the screen. The Rectangle function following SetROP2 draws a rectangle with the color set by the SetROP2 function and clears the earlier rectangle. The next rectangle is drawn a pixel ahead of the previous one. This gives a smooth movement to the rectangle.

3.Add the message handler OnLButtonDown to the view class and type the following code for the function in the RaceView.cpp file:

THREADS

Chapter 6

 

143

 

 

 

 

 

void CRaceView::OnLButtonDown(UINT F,CPoint p)

{

// Create an object of COLORREF type to store color //for rectangle

COLORREF color;

//Initialize the COLORREF object color = RGB(255,0,0);

//Call the race function with the object number and //color for

//the object as parameters

race(1,color);

//Reset the COLORREF object color = RGB(255,255,0); race(2,color);

color = RGB(0,255,0); race(3,color);

color = RGB(255,0,255); race(4,color);

color = RGB(0,0,0); race(5,color);

color = RGB(0,255,255); race(6,color);

}

This message handler is invoked when you click the left mouse button. The message handler executes the race function six times to create six rectangles of different colors.

Because this is a single-threaded application, all the rectangles are created and moved one after the other, as shown in Figure 6-2, and, therefore, there is no excitement for the race.

Next, you are going to create a multithreaded application for handling the same game.

Creating a Multithreaded Application

You just created an application that uses a single thread to handle multiple tasks. Recall that in the car-racing game, you need to run multiple rectangles simultaneously. You now are going to create a multithreaded application that enables

144

Part I

INTRODUCING VC++.NET

 

 

 

FIGURE 6-2 A single-threaded application displaying the different rectangles created in it

multiple bands to move from the left to the right of an application window. To create a multithreaded application, create an SDI application, Race, and make the following changes to the code generated by the wizard:

1.Add the global function in the RaceView.cpp file:

//Create a global variable to trap the number of threads static int flg=0;

UINT race(LPVOID p)

{

//Increment the global variable when the race function

//is called

flg=flg+1; int y = flg;

CClientDC *cdc = (CClientDC *)p;

//Assign a random color to the COLORREF object

//The rand function generates a random number.

//Divide the number by 255 and assign the remainder to

//the r,g,b parameters of the RGB function

COLORREF col=RGB(rand()%255,rand()%255,rand()%255);

CBrush br(col);

cdc->SelectObject(br); int x=10;

THREADS

Chapter 6

 

145

 

 

 

 

 

y=y*60;

while(x<690)

{

cdc->Rectangle(0,y,x+25,y+40); for(int j=0;j<=100000;j++); x++;

}

return 0;

}

You must declare the thread function, race, as global. You also declare a global flag variable that you increment when the race function is called to track the number of threads created. You also use this variable to set the top-left corner of the rectangle.

The race function traps an LPVOID type of object passed by the AfxBeginThread function. You need to typecast the LPVOID type of object to a CClientDC type to refer to the client area of the application window. The function selects a random color for the objects in the client area and draws a rectangle with the color.

CAUTION

If you do not create a global thread function, the application results in a compilation error.

2.Add the message handler OnLButtonDown to the view class and type the following code for the message handler function in the RaceView.cpp file:

void CRaceView::OnLButtonDown(UINT F,CPoint p)

{

CClientDC *dc=new CClientDC(this); for(int s=1;s<7;s++)

{

AfxBeginThread(race, (void

*)dc);

}

}

146

Part I

INTRODUCING VC++.NET

 

 

 

The message handler creates a pointer to the client area of the application window and creates six threads by using the AfxBeginThread function. The AfxBeginThread creates a thread and passes the address of the race function for execution. The second parameter of the function passes the pointer to the client area of the application window to the race function. Before passing the pointer, it is typecast to the void type because the second parameter of the AfxBeginThread function is defined as LPVOID. void is implicitly converted to the LPVOID type.

Figure 6-3 displays the output of the code.

FIGURE 6-3 A multithreaded application displaying the different rectangles created in it

Terminating Threads

Once the thread is done with its associated task, you need to ensure that the thread releases its hold on all resources and is removed from memory. To accomplish this, you need to terminate a thread. A thread can be terminated either normally or prematurely.

Normal Termination

Often, while working in an application you would have issued a print job in parallel to the ongoing task, say typing. Assuming that the application implements

THREADS

Chapter 6

 

147

 

 

 

 

 

the print job using threads, how does the application terminate the thread? The method is fairly simple. Once the thread function executes the print job and the control exits the function, the application terminates the corresponding thread. This refers to normal termination of threads.

So, how do you implement the termination process in your application? Well, if you are to terminate worker threads, you can use the AfxEndThread function (member function of the CWinThread class) or a return statement to exit the thread function, thereby terminating the thread. To indicate the successful execution of the thread, you can code your thread function to return some specific values. The following is the syntax of the AfxEndThread function.

void AFXAPI AfxEndThread(UINT nExitCode, BOOL bDelete = TRUE);

In the preceding code:

nExitCode. Indicates the execution status of the thread. To indicate successful completion, typically this variable is set to 0.

bDelete. Indicates whether the thread is to be terminated or not. Typically this flag is set to TRUE.

The method to terminate a user-interface thread is the same as that used to terminate a worker thread, except that you will have to invoke Win 32 API’s PostQuitMessage function from within the user-interface thread, instead of the AfxEndThread function. The syntax of the PostQuitMessage function is as follows:

Void PostQuitMessage(int nExitCode);

Here, nExitCode indicates the exit code of the thread. As stated before, 0 is used to indicate successful execution of the thread function.

Premature Termination

Continuing with the previous example, assume that you cancel the print job issued. This requires termination of the background thread that is handling the print job. To handle such premature termination of threads, you invoke the AfxEndThread function along with a single parameter, which is the exit code.

148

Part I

INTRODUCING VC++.NET

 

 

 

Synchronizing Threads

As discussed earlier, in a multithreaded application, all threads work asynchronously, meaning they run independent of each other. Hence, at no point in time can you predict which thread is being executed.

To understand this concept better, consider the example of an airline reservation application, which is an asynchronous multithreaded application. This application has two threads: one to handle the queries for seat availability, say Thread A, and the other for updating the seat availability based on any reservation or cancellation, say Thread B. Both of these threads access common data — the number of seats available. Assume a situation in which Thread B is updating the seats available for flight F001 and, simultaneously, Thread A is handling a query to retrieve the number of seats available for the same flight. This might work out smoothly, but at times, you might get either some unpredictable results or erroneous output

— the simple reason being that both threads are running in parallel.

You can avoid such situations by synchronizing the threads of the application. Synchronizing threads means that no thread can access a resource while another thread is using the same resource. Now, if you consider the scenario of the airline reservation application, Thread A will not be able to access the data until Thread B releases the resource. This will enable your application to provide error-free results.

To synchronize threads, MFC provides you with the following synchronization classes:

CSyncObject

CMutex

CCriticalSection

CSemaphore

CEvent

The following sections detail each of these classes in turn.

CSyncObject

The CSyncObject class is a pure virtual class. It provides the basic functionality for synchronization objects. A synchronization object is an object that multiple threads can access synchronously. The CSyncObject class is derived from the CObject class

THREADS

Chapter 6

 

149

 

 

 

 

 

and is the base class for other synchronization classes, such as CMutex, CCriticalSection, CSemaphore, and CEvent classes. In addition to the constructor, the class consists of Lock and Unlock methods. You use the Lock method to access the synchronization object and the Unlock method to release the synchronization object.

NOTE

You need to include the afxmt.h header file in your application to use the synchronization classes.

CMutex

The CMutex class is derived from the CSyncObject class. This class enables you to create synchronized threads by creating a mutex. A mutex is a synchronization object that enables only a single thread to gain exclusive access to a resource. It is ideal to use a mutex in an application in which you need to permit only one thread to gain access to the resources. In the example of the airline reservation application discussed earlier, it would be ideal if you were to create a mutex for the application and thus avoid any simultaneous access.

To use a CMutex object in your application, you need to create a data member of the CMutex class in the class that you want to control. From within the constructor of the controlled class, invoke the constructor of the CMutex member variable. The syntax for the constructor of the CMutex class is as follows:

CMutex(

BOOL InitialAccess,

LPCTSTR MutexName,

LPSECURITY_ATTRIBUTES Security

);

A brief description of the parameters accepted by the constructor follows:

InitialAccess. Specifies whether or not the resource controlled by the mutex is initially accessible to the thread that is creating the CMutex object. You need to specify TRUE if the resource is accessible, and specify FALSE otherwise.