Click here to Skip to main content
15,867,756 members
Articles / Desktop Programming / MFC

Using User-Interface Threads

Rate me:
Please Sign up or sign in to vote.
4.25/5 (18 votes)
16 May 2000 407.8K   132   99
Learn tricks on how to create and use a User-Interface thread

Introduction

I've never done a user-interface thread that has windows or controls. On the other hand, the complement of a worker thread is not necessarily a thread that has user-interface objects. The complement of a worker thread is a thread that has a message pump. This is a subtle but important distinction, but one that I have either needed or exploited, depending on circumstances.

Apartment Threading

I don't pretend to be an expert on "apartment threading".  I fell into it because I needed to know a small number of things about it, specifically, how to get an SAPI-enabled application to run.

The key to "Apartment threading" is that an object which is initialized should be initialized in a particular thread and all operations on that object must be performed from that same thread. Trying to do operations on the same object from other threads is not guaranteed to work correctly. The point of this being that the object can be then written without worrying about incorporating thread-safe operations, because only one thread ever accesses it.

The Message Pump

What I needed to run SAPI in a separate thread was not a worker thread, but a thread with a message pump. The thread does not have to have GUI objects, and hence the name "User-Interface Thread" is a serious misnomer. The user interface may not even be relevant.

What SAPI requires is a message pump. So I had to start a "user-interface" thread. There were a couple problems, so this essay tries to capture my experience.

Creating a User-Interface Thread

First, there is the question about how to create the thread. What you first need is a CWinThread-derived class, which is then necessarily a CCmdTarget-derived object. Use the ClassWizard to create it, and you will get all the correct declarations. For purposes of this essay, it will be called CMyThread.

Now, you would expect to use AfxBeginThread, in the approved fashion, doing:

C++
CMyThread * thread = AfxBeginThread(RUNTIME_CLASS(CMyThread));

Well, it isn't quite that easy. It should be, but it isn't. What's missing here is a way to pass initial parameters in to the thread! 

Consider a couple cases: you don't have a CMyThread object until the AfxBeginThread completes. But by the time you get control, the thread may already be running, and therefore it may be too late to set any values.

There are a couple solutions to this. One is to create the thread suspended, by providing the CREATE_SUSPENDED flag. Note that because this does not default, we have to provide the intermediate parameters, the thread priority and the call stack size, so provide the values which are the defaults:

C++
CMyThread * thread = AfxBeginThread(RUNTIME_CLASS(CMyThread),
                                    THREAD_PRIORITY_NORMAL,
                                    0, // stack size
                                    CREATE_SUSPENDED);

And upon completion, you can then set any member variables in the class, and explicitly resume the thread:

C++
thread->m_Whatever = ...;
thread->m_OtherThing = ...;
thread->ResumeThread();

I chose to do the two-step method suggested in the AfxBeginThread documentation:

C++
CMyThread * thread = new CMyThread();
thread->m_Whatever = ...;
thread->m_OtherThing = ...;
thread->CreateThread();

I did this just because I did not like to explicitly specify the defaults.

The Thread Function

OK, you've already done worker threads. You know that you provide a function that is called to execute the thread body, and the thread terminates when this function terminates, the thread ends. But what about a user-interface thread? Where is your function?

The answer is that it is CWinThread::Run is the thread function. This is the message pump. When it exits, your thread terminates.

Using the Thread Function

The key methods you might be interested in are virtual methods of your class. You can create these using ClassWizard:

BOOL PreTranlsateMessage(LPMSG msg) allows you to process messages before letting them be handled by the usual message dispatch. If you process the message at this point and do not want it dispatched, you must return TRUE. If you have processed it and want normal processing to continue for it, or you don't want to process it yourself, you return FALSE.

BOOL OnIdle(LONG count) is called as long as you have "idle tasks" to do and there are no other messages. The first time it is called for idle processing the count is 1, and the count is incremented on each subsequent call. If you return TRUE you will be called again, and again, until you return FALSE. Any time there is a new message to be processed, the counter is reset to 0. If you return FALSE, the message loop blocks until a message comes in.

BOOL InitInstance() is where you can do any setup for your class. For example, this is where I call CoInitialize(NULL) to initialize the COM subsystem to support the SAPI objects I'm calling. Note that CoInitialize is thread-specific and must be called for each thread that is calling ActiveX objects. If InitInstance fails, you should return FALSE. This will cause the thread to terminate.

void ExitInstance() is where you do any cleanup for your class. It is called after the message pump terminates. This is where I do CoUninitialize() to clean up the COM resources allocated for the thread.

BOOL PostThreadMessage(UINT msg, WPARAM wParam, LPARAM lParam) allows you to post a message to the thread. It returns TRUE if successful and FALSE if error.

Using the Message Map

The Message Map for a CWinThread does not support all messages, although it won't complain if you try to put something unsuitable in it. You need to use the macros

C++
ON_THREAD_MESSAGE(UINT msg, LRESULT (handler *)(WPARAM, LPARAM))
ON_REGISTERED_THREAD_MESSAGE(UINT msg, LRESULT (handler *)(WPARAM, LPARAM))

The ON_THREAD_MESSAGE macro requires that the msg parameter be a constant, typically a (WM_APP+n) value. The ON_REGISTERED_THREAD_MESSAGE macro requires that the msg parameter be the name of a UINT variable that holds the Registered Window Message. If you need to understand more about how to form these messages, read my essay on Message Management.

The message handler is a function whose signature is as shown. The WPARAM and LPARAM of the PostThreadMessage are passed to the handler method. The return value is always ignored, and is traditionally 0.

General Queuing

You can queue messages up for processing simply by using PostThreadMessage. You don't need a queue, interlocks, or any other mechanism to handle queuing. However, because you are doing PostThreadMessage, you must make sure that the object you are passing is still in existence at the time you process the message. This means that if you are not passing simple integer values as WPARAM or LPARAM, for example, if you want to pass a CString, you must pass a pointer to an object on the heap and delete it when you've processed the message. You cannot pass a pointer to anything on the stack.

However, this also suggests that a "user-interface" thread can also be thought of as a "queued event thread", where you use PostThreadMessage to add entries to the queue, and dispatch the dequeuing via the MESSAGE_MAP.

Terminating the Loop

So we know how to start the thread, and initialize the thread, but how do we stop the thread? Just like any other message pump, as it turns out. Just post a WM_QUIT message. It is unfortunate that Microsoft has not chosen to be consistent, but they actually require in the PostThreadMessage that you explicitly provide the WPARAM and LPARAM values, which are, in this case, 0. So when you get to the point where you are read to shut down the thread, just do:

C++
thread->PostThreadMessage(WM_QUIT, 0, 0);

and the thread shuts down.

Hazards

It is a bad idea to mix thread control using PostThreadMessage and UI objects such as message boxes and dialog boxes. When a thread message is received by GetMessage, it has a NULL window handle. This means that it cannot be dispatched via DispatchMessage. Special handling is required for thread messages, which is built into the message pump of CWinThread::Run. What this means is that if you call DoModal or AfxMessageBox, MessageBox, or any similar function that spawns a new message loop any posted thread messages will be lost. Microsoft suggests using a message hook function to intercept messages under these conditions. Consequently, if you are going to use PostThreadMessage to a UI thread, you should follow the advice given in my essay about worker threads and treat the UI thread as if it were a worker thread insofar as spawning child dialogs.


The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.

If you have any questions or comments about this article, please leave a note below.
Copyright © 1999 The Joseph M. Newcomer Co. All Rights Reserved.
www.flounder.com/mvp_tips.htm

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.


Written By
Retired
United States United States
PhD, Computer Science, Carnegie Mellon University, 1975
Certificate in Forensic Science and the Law, Duquesne University, 2008

Co-Author, [i]Win32 Programming[/i]

Comments and Discussions

 
QuestionWhat king of thread should I use? Pin
Julian Popov8-Mar-08 6:46
Julian Popov8-Mar-08 6:46 
AnswerRe: What king of thread should I use? Pin
Joseph M. Newcomer8-Mar-08 18:25
Joseph M. Newcomer8-Mar-08 18:25 
GeneralUI thread without ui interface is a better "worker thread" when need message pump Pin
hamo200822-Oct-07 20:36
hamo200822-Oct-07 20:36 
Great!

UI thread without ui interface is a better "worker thread" when need message pump.

http://www.ucosoft.com
----
You have noting to lost, but everything to gain.

GeneralRe: UI thread without ui interface is a better "worker thread" when need message pump Pin
Joseph M. Newcomer24-Oct-07 12:38
Joseph M. Newcomer24-Oct-07 12:38 
QuestionTerminating a thread... Pin
Salvatore Castellano17-May-06 5:45
Salvatore Castellano17-May-06 5:45 
AnswerRe: Terminating a thread... Pin
Luca Piccarreta19-Jun-06 8:46
Luca Piccarreta19-Jun-06 8:46 
GeneralGive us an example! Pin
Kybert18-Apr-06 22:34
Kybert18-Apr-06 22:34 
GeneralRe: Give us an example! Pin
Joseph M. Newcomer19-Apr-06 3:06
Joseph M. Newcomer19-Apr-06 3:06 
GeneralMutlipule threads Pin
Tom Wright6-Oct-04 6:58
Tom Wright6-Oct-04 6:58 
GeneralRe: Mutlipule threads Pin
Joseph M. Newcomer12-Oct-04 19:01
Joseph M. Newcomer12-Oct-04 19:01 
GeneralWM_TIMER in UI Thread Pin
sudiptam4-Oct-04 20:28
sudiptam4-Oct-04 20:28 
GeneralRe: WM_TIMER in UI Thread Pin
ThatsAlok27-Jul-07 0:20
ThatsAlok27-Jul-07 0:20 
GeneralSockets Multithreading Pin
Member 87950420-Jul-04 20:52
Member 87950420-Jul-04 20:52 
Generalocx with a non-modal dialog Pin
conchi26-Mar-04 2:27
conchi26-Mar-04 2:27 
GeneralRe: ocx with a non-modal dialog Pin
Joseph M. Newcomer26-Mar-04 4:39
Joseph M. Newcomer26-Mar-04 4:39 
GeneralRe: ocx with a non-modal dialog Pin
conchi28-Mar-04 20:35
conchi28-Mar-04 20:35 
GeneralRe: ocx with a non-modal dialog Pin
Joseph M. Newcomer28-Mar-04 20:58
Joseph M. Newcomer28-Mar-04 20:58 
GeneralRe: ocx with a non-modal dialog Pin
conchi28-Mar-04 21:40
conchi28-Mar-04 21:40 
GeneralThread Termination Pin
Acreff26-Jan-04 9:32
Acreff26-Jan-04 9:32 
GeneralRe: Thread Termination Pin
Acreff26-Jan-04 11:52
Acreff26-Jan-04 11:52 
GeneralRe: Thread Termination Pin
Joseph M. Newcomer27-Jan-04 0:17
Joseph M. Newcomer27-Jan-04 0:17 
GeneralRe: Thread Termination Pin
Acreff27-Jan-04 4:24
Acreff27-Jan-04 4:24 
GeneralRe: Thread Termination Pin
Joseph M. Newcomer27-Jan-04 8:08
Joseph M. Newcomer27-Jan-04 8:08 
GeneralRe: Thread Termination Pin
Acreff3-Feb-04 6:52
Acreff3-Feb-04 6:52 
GeneralRe: Thread Termination Pin
Joseph M. Newcomer26-Jan-04 14:12
Joseph M. Newcomer26-Jan-04 14:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.