Worker Threads

Worker threads & the GUI

Enabling controls

The problem is that when your worker thread is running, there are probably lots of things you shouldn't be doing. For example, starting a thread to do a computation. Then you'd have two threads running doing the same or similar computations, and that way madness lies (we'll assume for the moment that this actually is a Bad Thing).

Fortunately, this is easy. Consider your ON_UPDATE_COMMAND_UI handler

void CMyView::OnUpdateInvertImage(CCmdUI * pCmdUI)
    {
     pCmdUI->Enable(!running && (whatever_used_to_be_here));
    }

Generalizing this to cover other menu items is left as an Exercise For The Reader. However, note that this explains why there is that assignment "running = FALSE;" at the end of the thread handling routine above: it explicitly forces the running flag to reflect the running status of the thread. (Well, if you are being very pedantic, note that it is possible to start another thread before the current one finishes if the current one does not quickly get back to test the running flag, so you may wish to use a separate Boolean variable to indicate the thread state. Set it before the thread starts, and clear it only after the thread loop completes. For most thread usages, a single running flag usually suffices.

Don't touch the GUI

That's right. A worker thread must not touch a GUI object.This means that you should not query the state of a control, add something to a list box, set the state of a control, etc.

Why?

Because you can get into a serious deadlock situation. A classic example was posted on one of the discussion boards, and it described something that had happened to me last year. The situation is this: you start a thread, and then decide to wait for the thread to complete. Meanwhile, the thread does something apparently innocuous, such as add something to a list box, or, in the example that was posted, calls FindWindow. In both cases, the process came to a screeching halt, as all threads deadlocked.

Let's analyze these two situations in some detail, so you get a sense of what happened.

In my case, the list box sent a notification, via SendMessage, to its parent. This means the message went to its parent thread. But the parent thread was blocked, waiting for the thread to complete. But the thread couldn't complete until it could run, and guess what: the SendMessage was a cross-thread SendMessage, which would not return until it was processed. But the only thread that could process it was blocked, and couldn't run until the thread completed. Welcome to the world of deadlock.

The FindWindow problem was quite similar. The programmer had specified finding a window with a particular caption. This meant that the thread running FindWindow had to SendMessage a WM_GETTEXT message to the window whose handle it had just found via EnumWindows. This message could not be processed until the thread that owned the window could execute. But it couldn't, because it was blocked waiting for the thread to finish. Deadlock. So note that although you should not touch the GUI thread explicitly, you must also not touch it implicitly, through such innocuous-seeming operations such as FindWindow, or you can, and will, experience deadlock. Under these conditions, by the way, there is no recovery. You can kill the process, which explicitly terminates all threads, but this is neither elegant nor, as the warning box tells us, necessarily safe.

How do you get around these problems?

In my case, I had just been sloppy. I actually know better, and have written as much. So much for being an expert. The workaround in almost all cases is that you must never to a SendMessage to a GUI object. In very rare cases, you can do a PostMessage, although this usually won't work because you need to pass in a pointer to a string, and cannot tell when the operation has finished so you can't tell when to release the string. Any string you pass in must be allocated either in static storage (as a constant), or on the heap. If it is allocated in writeable static storage, or on the heap, you need to know when the value can be reused or freed. PostMessage does not allow you to do this.

Therefore, the only solution is to use a user-defined message, and post it to the main GUI thread (usually the main frame, but frequently a CView-derived object). The main GUI thread then handles the SendMessage to the GUI object, and, knowing then when the operation has completed, is able to free up any resources.

You must not send a pointer to a stack-allocated object in a PostMessage call. By the time the operation begins to execute, the chances are excellent that the object will have been removed from the stack. The results of this are Not A Pretty Sight.

This essay will not go into describing how to manage user-defined messages. That is covered in my essay on Message Management. We will assume that there are some user-defined messages, with names like UWM_ADDSTRING, that are already defined.

Here's some code that adds window names to a list box:

void CMyDialog::AddNameToControl(CString & name)
    {
     CString * s = new CString(name);
     PostMessage(UWM_ADDSTRING, 0, (LPARAM)s;
    }

To the collection of handlers in CMyDialog.h, I add the declaration

afx_msg LRESULT OnAddString(WPARAM, LPARAM);

To the MESSAGE_MAP of my class, I add

    ON_REGISTERED_MESSAGE(UWM_ADDSTRING, OnAddString);

or

    ON_MESSAGE(UWM_ADDSTRING, OnAddString)

(If you're curious about the distinction, read my essay on Message Management).

The handler then looks like this:

LRESULT CMyDialog::OnAddString(WPARAM, LPARAM lParam)
    {
     CString * s = (CString *)lParam;
     c_ListBox.AddString(*s);
     delete s;
     return 0;
    }

The sender of the message must allocate the value being sent on the heap, which it does via the new operator. The message is posted to the main GUI thread, which eventually routes it to the OnAddString handler. This particular handler knows that the message is destined for a particular control, c_ListBox (if you don't understand how to get control variables for controls, read my essay on the subject). Note that we could have put the destination control ID, or the destination CWnd-class variable, in wParam, and made this more general. The handler calls the AddString method. When this call completes, it is now known that the string value is no longer required (this would be different if we had an owner-draw listbox without LBS_HASSTRINGS, but if you already know how to do that, the solution should be evident). Therefore, we can now delete the heap-allocated object, which for us is a CString.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Programs must be written for people to read, and only incidentally for machines to execute.”