This will hopefully become a more organized article with code samples, but I need to record my results for now. Here’s the practical problem I encountered in the application I am working on: several message boxes initiated by worker threads would pop up simultaneously confusing the user and wrecking havoc.
So, the task is: you want to display a notification (message box) from a worker thread. Sometimes it is fire-and-forget, sometimes you want to know the answer (“Do you want to continue? Yes|No”). I will assume the answer is important, since this case is more complicated.
Here are some facts that I discovered:
Fact #1: Dismissing a message box may enable the parent even if there are other pending message boxes. I am not sure what is the exact algorithm here, but if you have multiple message boxes on the screen, there is at least one of them that would re-enable the parent when dismissed. This will render all other message boxes effectively modeless. The user can continue to work on the parent and maybe even create more message boxes.
Fact #2: Calling
MessageBox() from a worker thread effectively converts that thread to a UI thread with the message pump.
Fact #3: If the UI thread is blocked,
MessageBox() will also block, even when called on a worker thread. The reason for that is that
MessageBox() internally called
DialogBox2() function in user32.dll, which calls
NtUserCallHwndParamLock(). I am not sure what that function does, but it blocks if the UI thread does not respond.
Fact #4: If you use
Invoke) to render the message boxes on the UI thread, the message boxes can still appear in parallel. In fact, they become recursive. If you look at the call stack, it looks like one message box calls another.
This happens because the parent window is not dead while showing a message box: it is just disabled. The message box runs its own message pump, and when another thread sends a message instructing the parent window to show another message box, it will be happily processed, showing the second message box on top of the first.
Bottom line: “serializing” message box calls via
SendMessage to the UI thread does not work.
Fact #5. If the UI thread is blocked while we send it “show that message box” messages, the message boxes will appear in sequence, starting with the latest. This puzzled me for a while, but after some research, I found the truth. The message boxes are still being called recursively, only they don't have a chance to show. Showing a window is a complex process that requires handling multiple
WM_ messages. When
MessageBox() function is called and starts running the inner message loop, there is no window yet. It will appear a little bit later. However, the first message that is processed in the new loop is a request for another message box, which effectively defers creation of the window until later. Only the last message box in the chain has a chance to display itself. Once it is dismissed, previous message box(es) take turn.