MFC distinguishes two types of threads: worker threads, for background tasks like spreadsheet recalculation, and user-interface (UI) threads, for gathering input and displaying output. Every program's main thread is a UI thread. Threads are activated with calls to AfxBeginThread, which, through parameter overloading, has a form corresponding to each of the two types of threads. Worker threads are simple to start and use because they utilize instances of MFC's CWinThread class (created automatically for you by the call to AfxBeginThread), in contrast to UI threads, which require you to derive a class from CWinThread and override certain functions. In operation, UI threads differ from worker threads in that they spend time in their Run method, which does message pumping (that is, calls to TranslateMessage, DispatchMessage, and OnIdle). Worker threads rely on the main thread or another UI thread for message pumping.
Typically, worker threads are used for background tasks that run once and then terminate, while UI threads are used for threads that interact with the user. You might use UI threads, for instance, in an application that creates several independent windows, each of which occasionally needs to do extensive processing. In a single-threaded situation, this could interfere with the responsiveness of the user interface. A UI thread for each window would create independent message processing and ensure that each remained responsive.
In many cases, neither of these forms of thread are precisely what you need. There's often a need for repetitive scheduled or event-driven activities. Neither worker threads nor UI threads contain the requisite synchronization or timing mechanisms. What's called for is a persistent thread that waits with little or no overhead until the time or situation is right for it to spring into action. Internal message processing isn't generally necessary because you can rely on the application's main UI thread for that.
To address these and other considerations, I created CMultiThread, which I'll discuss in detail later. First, I'll discuss worker and UI threads as provided by MFC and some of the issues that arise when using them or CMultiThread.
Since both worker threads and UI threads are derived from CWinThread, there is little difference in core functionality; the main differences are convenience and overhead. Creating a worker thread can be as simple as creating a function and passing it to AfxBeginThread:
CWinThread* AfxBeginThread (AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
The simplest, most common usage looks like this:
myWinThread = AfxBeginThread(pMyFunction, pParam);
where pMyFunction points to a thread control function of the form:
UINT MyFunction( LPVOID pParam );
The pParam passed to AfxBeginThread is the app-defined parameter to this control function.
As mentioned, you don't have to create a CWinThread object or derive a class from CWinThread when you start a worker thread—AfxBeginThread does that for you. If you wish, you can specify a priority. (You'll probably want to do this if you want your worker thread to run at a lower priority than UI threads, since the default is THREAD_
PRIORITY_NORMAL; THREAD_PRIORITY_BELOW_NORMAL or THREAD_PRIORITY_LOWEST would be
more typical values for background printing or recalculation threads.) If you don't specify otherwise, the thread will terminate when your function exits.
Let's look at the other parameters to AfxBeginThread. nStackSize controls the size of the thread's local stack. There's little need to specify this since the default behavior is the size of the application's stack and Windows 95 and Windows NT will enlarge it automatically up to 1MB. If you're starting many threads, you might want to specify a low value for each to avoid using memory unnecessarily.
dwCreateFlags specifies whether the thread will run immediately or will be created in a suspended state (to be started with a call to the ResumeThread member function). The default is zero, which indicates an immediate start. If you want a later start, supply a value of CREATE_SUSPENDED for this parameter.
lpSecurityAttrs points to a structure that specifies the security attributes for the thread, which determine the kind of access to files and certain other system resources (Windows NT only). The default value of NULL means that the thread inherits the security attributes of the calling thread.
For an example of exactly how this would be implemented in the case of background spreadsheet recalculation, examine the MTRECALC sample application included with Visual C++¨ 4.x.
Creating a UI thread is more complex. Before you call AfxBeginThread to start a UI thread, you must derive a class from CWinThread and override certain members. The call to AfxBeginThread to start a UI thread is like the call for worker threads, except that the controlling function and its parameters are replaced by a pointer to a CWinThread-derived object. Instead of running a C function, this version of AfxBeginThread will call the InitInstance and Run members of the supplied object.
When you derive a class for this use, you must override InitInstance, if only to return a value of TRUE (or you can use it to do actual work). You may also override ExitInstance to clean up at the termination of the thread. You might want to override OnIdle if you have thread-specific background activity like garbage collection; however, your main thread may have its own OnIdle processing to take care of.
Your Run method gets messages for the windows created by the thread. It sends them to PreTranslateMessage, which you may want to override, then sends them through MFC's standard message routing.
CWinThread has data members that contain the thread ID, a thread handle, and a pointer to the application's main window. It also has an m_bAutoDelete member, which determines whether or not the object should be destroyed when the thread terminates.
CWinThread has methods to get and set the thread's priority, and to get a pointer to the thread's main window. In terms of external control, the members that you're most likely to use are Suspend–Thread and Resume–Thread. The first increments a thread's suspend count, and the second decrements it. When the OS dispatches threads, it declines to dispatch any with a nonzero suspend count. Obvious consequences are that calls to SuspendThread and ResumeThread must be paired, and a single call to ResumeThread will not start up a thread if it has been suspended more than once.
You must declare and implement your thread class using the DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE macros. When you're ready to start your thread, use the following form of AfxBeginThread:
CWinThread* AfxBeginThread (CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
To see how this can be used to paint a window with multiple bouncing balls, rectangles, or colored lines, each in its own thread, examine the MTGDI sample application included with Visual C++ 4.x.