Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By MICKEY WILLIAMS and David Bennett

Using Worker Threads

Worker threads are handy for any time you want to do something such as calculations or background printing. Worker threads also are useful when you need to wait on an event to occur, such as receiving data from another application, without forcing the user to wait. Let's face it—most users are not known for their patience.

Creating a worker thread is relatively simple; the hard part comes later, when you need to make sure that your thread plays well with others—but more on that later, in the section, "Thread Synchronization." To get a worker thread up and running, you implement a function that will be run in the thread, and then create the thread with AfxBeginThread(). Although you may choose to create your own CWinThread-based class, this is not necessary for worker threads.

Starting the Thread

An MFC thread, whether a worker or user-interface thread, is started with a call to AfxBeginThread(). This function is overloaded to handle the creation of the two flavors of threads, but for now, let's look at the variety used to create worker threads. Here is the prototype for this function:

CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc,
        LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL,
        UINT nStackSize = 0, DWORD dwCreateFlags = 0,
        LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

The first parameter is a pointer to the function that will run inside the thread. As you will soon see, this function can take a single parameter, which is passed as the second parameter. Generally, this is a pointer to a data structure of some sort.

Each thread also may have its own priority. This parameter may be set to any of the values accepted by SetThreadPriority(), which is discussed later.

Because each thread executes independently, it must have its own stack to keep track of function calls and the like. The size of the stack may be specified in the call to AfxBeginThread().

In most cases, you probably will want your thread to start doing its thing right off the bat. However, you may specify the CREATE_SUSPENDED flag in the dwCreateFlags parameter to create a thread that is suspended upon creation. This thread will not begin executing until ResumeThread() is called.

Optionally, you may specify a SECURITY_ATTRIBUTES structure to specify security parameters to be used with the thread.

AfxBeginThread() returns a pointer to the newly created CWinThread object. You squirrel this away somewhere so that you can work with the member functions of CWinThread later.

When you call AfxBeginThread(), it creates a new CWinThread object for you and calls its CreateThread() member function. At this point, unless you have specified CREATE_SUSPENDED, your new thread begins to execute the function you specified, and the thread that called AfxBeginThread() goes on its merry way.

The new thread continues to execute the function specified until that function returns, or until you call AfxEndThread() from within the thread. The thread also terminates if the process it is running in terminates.

Implementing a Thread Function

The sole purpose in life for a worker bee is to make honey. The sole purpose in life for a worker thread is to run its thread function, or controlling function, as Microsoft calls it in its documentation. In general, when the thread starts, this function starts. When the function dies, the thread dies.

First, your thread function should have a prototype that looks like this:

UINT MyThreadProc(LPVOID pParam);

All thread functions take a single 32-bit argument. Although you could pass a single value here, such as an int, it generally is more useful to pass a pointer to a structure or other object that can hold more information. This structure also may be used to return information to the rest of your application.

You could use the following simple thread function to encrypt a string, for example:

UINT MyThreadProc(LPVOID pParam)
{
    if(pParam == NULL)
        AfxEndThread(MY_NULL_POINTER_ERROR);
    char *pStr = (char *)pParam;
    while(*pStr)
        *pStr++ ^= 0xA5;

    return 0;
}

You could use this function in a thread created like this:

AfxBeginThread(MyThreadProc, pMySecretString);

After the thread is created, it starts executing until it either discovers that the pointer passed to it is null, or it finishes with the string. In either case, whether the function calls AfxEndThread() or simply returns, the function stops executing, its stack and other resources are deallocated, and the CWinThread object is deleted.

Accessing a Thread's Return Code

The exit code specified when the function returns or calls AfxEndThread() may be accessed by other threads in your application with a call to ::GetExitCodeThread(), which takes a handle to the thread and a pointer to a DWORD that will receive the exit code. The handle to the thread is contained in the m_hThread member of CWinThread, so it should be no problem to pass this to ::GetExitCodeThread(), right?

Well, there's a catch: By default, the CWinThread object is deleted as soon as the function returns or calls AfxEndThread(). You can get around this in one of two ways.

First, you can set the m_bAutoDelete member of CWinThread to FALSE, which prevents MFC from deleting the object automatically. You then can access the m_hThread member after the thread terminates. However, you now are responsible for deleting the CWinThread object.

Alternatively, you can use ::DuplicateHandle() to create a copy of the m_hThread member after the thread is created. You must be certain that you copy the handle before the thread terminates, however. The only way to be absolutely certain of this is to specify CREATE_SUSPENDED when the thread is created, copy the handle, then call ResumeThread() to start the thread. As you can see, this process gets to be a bit involved; thus, it generally is preferable to change the m_bAutoDelete member.

The exit code value returned by GetExitCodeThread() contains STILL_ACTIVE if the thread is still running, or if the thread has terminated, the return code that the thread passed when it returned or called AfxEndThread(). Note that STILL_ACTIVE is defined as 0x103 (259 decimal), so avoid using this as a return code from your thread.

Share ThisShare This

Informit Network