Re: UI Thread V Worker thread
From: Dave Hall (dhall_at_nospam.deboy.com)
Date: Wed, 14 Jul 2004 01:38:18 -0400
I'm not sure what the official supported answer is to your question, or even
what best practices would suggest for every situation, but I can tell you
what I've been able to do with no known problems to date. Still, just
because I haven't crashed anything yet, doesn't mean it's viable code,
therefore, I'd also like to hear a definitive answer from Microsoft on this
issue or even form other folks with more UI experience than me. Here's a
description of my problem and resolution.
I needed to display dialog with textual information gathered in a worker
thread, and have the worker thread block until the dialog is acknowledged so
it can proceed depending if the form returned btnOK or btnCancel. I needed
this dialog to keep the focus until it's closed like a modal Dialog, so I
first tried creating the form and calling ShowDialog() from the worker
thread. The modal form appeared to behave correctly, but I realized I also
had some user drawn buttons that still showed on the main form behind the
dialog, which were not hidden by the dialog. They needed to toggle color
based on other asynchronous events in other threads (e.g. timer fires to
flash On-Hold button periodically). These had to continue to change color
even when the pseudoModal dialog had the focus. Besides the buttons toggling
color, I also happen to be using a derivative of the OpenNetCF WaveOut API
wrappers to play several audio files in response to still other asynchronous
events (e.g. simulating a phone ringing). The problem I discovered with
creating a new instance of a form and calling ShowDialog() is that since the
main UI thread and also my audio thread both rely on window messages flowing
through their message pumps, they both quit working while the modal dialog
had the focus.
My next idea was to call Application.Run() with the form I wanted to show.
This fixed the message pump issue, because the new form has it's own message
pump now instead of diverting or suspending the one from the main UI.
Unfortunately, I had two new problems. The first problem is that
Application.Run() does not block until the form is closed, so I couldn't get
a return value to indicate the user's response the way I could with
ShowDialog(), which blocks. The second problem was that the user was now
free to click on the main UI form and take the focus away from the form
which was supposed to retain focus like a modal Dialog. I solved these two
problems as follows:
In order to get a return value from the form, I subclassed it and added a
new public method called WaitForClose(). In the FormLoad event, I initialize
a private unsignalled synchronization object (ManualResetEvent). In the
button events, I set the form ReturnValue, and in the FormClose event, I
signal the ManualResetEvent. The code that needs to know the return value
calls WaitForClose() which simply blocks until the ManualResetEvent is
signaled, and then returns this.ReturnValue.
Ok, that fixed problem 1, but what about the fact that I can't keep focus on
my pseudoModal form? Well, I cheated. I simply made the form full screen so
that it hides the main UI form, and then nobody can click on it. It's not
technically modal at this point, but it achieves the same results for me.
(The environment where my app runs is a touchscreen with no keyboard or task
bar, so no need to worry about Alt-Tab or other keystrokes that might change
focus, or anyone clicking on the task bar to change focus.)
That takes care of problem 2. One fairly obvious question now, would be why
I even worried about keeping the message pump running on the main UI if I
was just going to completely hide it with a new form. Well, the answer is
that I decided I didn't need to see the flashing buttons on the form while
the dialog was visible, but I did still need to hear the audio from the
OpenNetCF WaveOut thread, so even though there was no visible need for the
UI thread to keep running, I did need it to continue for the audio to
If I didn't need the audio threads to have a message pump running all the
time, I could have simply created a new form in the worker thread and
immediately called ShowDialog(). Without knowing the nature of the forms
you're using, I don't know if this would suffice for you, but maybe I've
thrown out a few new ideas you can consider.
I'll admit, it seems rather odd to me to call Application.Run(myForm) more
than once in an application, especially in different threads, but as far as
I can tell from the docs I've read, and my own experience, it's a legitimate
solution, and results in a separate message pump for each form called this
way. I'm assuming that the form needs to be created in the same thread in
which it is passed to Application.Run(), but I'm not sure if that's the case
or not. I know that having a full screen dialog is a bit of a hack too. I
might have been able to have the form capture the mouse when it's loaded,
but I didn't get around to trying that approach.
Any comments, cringing, shrieks, affirmations, or other feedback from any
MVPs or other gurus is welcome. Whaddaya think?
Good Luck Gravy,
"Gravy" <Gravy@discussions.microsoft.com> wrote in message
> Is it possible for a worker thread to create a form and show it just as
the UI thread normally does, or must a form be created and shown by the UI
thread only? Thereby if a worker thread wants to show a form it must ask the
Main form (the main UI thread) to create and show the form on its behalf.
> Thanks for any help