Worker Threads in MFC
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. Creating a worker thread is relatively simple.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().
An MFC thread, whether a worker or user-interface thread, is started with a call to AfxBeginThread().
Here is the prototype for this function:
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. AfxBeginThread() returns a pointer to the newly created CWinThread object. When you call AfxBeginThread(), it creates a new CWinThread object for you and calls its CreateThread() member function. 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.
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.. 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. You could use the following simple thread function to encrypt a string, for example:
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.
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(). 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. 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.
User-interface threads are similar to worker threads, because they use the same mechanisms provided by the operating system to manage the new thread. User-interface threads provide additional functionality from MFC, however, that enables you to use them to handle user-interface objects, such as dialog boxes or windows. To use this functionality, you have to do a bit more work than you did with worker threads, but this is still much simpler than using the Win32 API directly to set up a new thread to handle windows messages.
To create a user-interface thread, you use a slightly different version of the AfxBeginThread() function. The version used to create user-interface threads takes a pointer to a CRuntimeClass object for your thread class. MFC creates this for you with the RUNTIME_CLASS macro:
If you want to keep track of the MFC object for your thread, use this:
The default implementation of CWinThread::Run() provides a message pump for your new thread. You can override this function to do anything else you want your thread to do, although most user-interface threads will simply use the default implementation of CWinThread::Run(). In addition, you may override the PreTranslateMessage() function if you want to intercept messages before the message pump dispatches them, although most messages can be handled in the message map. Your thread class may implement a message map just as for any other class derived from CCmdTarget, as you saw in Chapter 3. However, you also may use a special message map macro, ON_THREAD_MESSAGE, to handle messages that are sent directly to the thread rather than to a given window. You can send messages directly to a thread with
CwinThread::PostThreadMessage(), as shown here:
pMyThread->PostThreadMessage(WM_USER+1, myWParam, myLParam);
This is similar to the ::PostThreadMessage() API call, which takes an additional parameter, the thread identifier.
Note that the first parameter specifies the message to post. This example uses WM_USER+1, which gives a valid user-defined message ID. To then handle the message you posted, the message map for your thread would look something like this:
The handler for your thread message looks like this:
A thread can terminate in several ways. Normally, a thread ends when the thread function it is running returns. For worker threads, this process is pretty straightforward. For user-interface threads, however, you generally don't deal with the thread function directly. The Run() member of CWinThread is actually the thread function that MFC uses by default to implement a message pump for the thread. This function exits after receiving a WM_QUIT message, which a thread may send to itself with a call to PostQuitMessage(), as in the previous example.
Additionally, if your user-interface thread manages an MFC window, as set in the m_pMainWnd member, MFC calls PostQuitMessage() for you when the main window is destroyed. In either case, user-interface threads call ExitInstance() before actually terminating. The Win32 API provides the TerminateThread() function, but using this method can have some very dire consequences. Unlike the previous methods of terminating threads gracefully, TerminateThread() stops the thread dead in its tracks, without any provisions for cleaning up allocated memory. More important, the thread that is terminated in this way may be interrupted in the middle of several different transactions which leaves the system in an indeterminate state. If you need to terminate a thread from outside that thread, try to use some form of communication to tell the thread to terminate itself gracefully.
Many developers, when they first learn how to create multiple threads in an application, have a tendency to go overboard and try to use a separate thread for everything an application does. This is not generally a good practice. Not only is it more work to create all those threads, but the effort involved in making sure that all threads cooperate may easily increase exponentially with the number of threads. They are extremely useful—even necessary—in many situations.
Creating a multithreaded application that works correctly is not a trivial task, however. You must give very careful consideration to how your threads will communicate with each other and how they will keep from stomping on each other's data. You must keep in mind that threads in Win32 may (and will) be preempted by the operating system. That is, any thread may be stopped right where it is and another thread allowed to run for a while. Thus, it is safest to assume that all threads are running simultaneously. Even though only one thread at a time actually is using a CPU, there is no way to know when or where a thread will be preempted, or what other threads will do before the original thread resumes execution.
|All times are GMT +5.5. The time now is 10:14.|