views:

65

answers:

4

At work we've got a very CPU-intensive Windows Forms application. It's running on a server with 24 cores.

As the Windows Forms model involves a single GUI thread processing a message pump, it seems that a rich, complex GUI is not making good use of the capabilities of the system. Am I wrong in saying this?

So my coworkers were discussing this. Our application just happens to involve two more-or-less independent forms. The idea came up to have the auxiliary form operate on its own dedicated thread, freeing up the app's main GUI thread to only process messages sent to the main form.

Presumably this would look something rather like this:

void CreateAuxiliaryForm()
{
    Action displayForm = () =>
    {
        var f = new AuxiliaryForm();
        f.ShowDialog();
    };

    displayForm.BeginInvoke(displayForm.EndInvoke, null);
}

Now, first of all, I don't know if this is safe to do at all. But my thinking is that since the two forms will be independent, it really ought to be OK for each one to have its own thread. Is this true?

I did say "more-or-less," and that's because I can't really say the two forms have no interaction whatsoever. But what my coworkers and I figured was that in any scenario in which the main form has to interact with the auxiliary form in some way (say by one handling an event raised by the other), we would simply need to be sure to use Invoke/BeginInvoke to send any GUI-related code to the appropriate message pump.

What are people's thoughts on this idea?

+1  A: 

This should be OK as long as GUI updates happens on respective threads (using Control.Invoke) and any access to shared state is made via thread-safe way.

BTW, CPU intensiveness may indicate some processing/calculation - you can perhaps introduce parallelism there. Of course, that may mean a lot of changes.

VinayC
+1  A: 

I don't think you can buy yourself much running separate forms on separate threads. Take, for example, my CRT monitor. It refreshes at a maximum rate of 70 frames per second. This is regardless of how many threads I devote to updating the UI; it just plain can't push more frames per second. Your average movie theater updates the screen at only 24 frames per second, and still delivers the illusion of continuous animation.

You can avoid having one form interfering with updates on another form simply by delegating time-intensive processing to background threads. Creating additional threads for the UI will likely result in the extra threads idling most of their lifetime away, especially if you are not dealing in graphics-intensive applications.

kbrimington
+3  A: 

Generally: Don't.

There are few possible reasons where threading the UI might help:

  • You are blocking the foreground thread for significant times (say > 5s) after a user action
  • You are blocking for notable time spans (>~300..500ms) not triggered by the user (e.g. through a periodic timer)
  • You have really expensive updates (rendering full-screen charts) and expect a dozen of these forms active.

Otherwise, there is no point, even for a "UI from hell" with hundreds of buttons and controls.

The only performance requirement for the UI is that it's smooth, meaning the user perceives UI responses to his actions as "instant" - which, for most actions is is 100..300ms (depending on the frequency of the action and the "size" of the response).

The downsides are many. WinForms is built on top of Win32 GUI, where threading "kinda works for top-level forms", but takes some care to get right. Internet Explorer uses spearate processes even, and some addins still manage to bring down all tabs when one page hangs.

peterchen
Well, I'm certainly not surprised by your advice to generally avoid this. That said, it was good to see that you listed a few scenarios where it might make sense.
Dan Tao
+2  A: 

I recently worked on a multi-form WinForms app that was very heavy on UI updates, i.e. it was spending most of its time in the forms' message-loops, specifically on timers and on painting operations. The individual forms themselves hardly interacted with one another at all, so cross-form UI dispatches wasn't an issue. In this case, I found that running dedicated UI threads for each form made a big difference to their responsiveness.

Btw, I prefer this pattern to launch a form on it's own message-loop, although your method should work as well:

new Thread(() => Application.Run(new Form1())).Start();
Ani
That's a good point, actually. I hadn't thought about it, but using `BeginInvoke` would actually just choke a threadpool thread, wouldn't it?
Dan Tao
@Dan Tao: Yes, absolutely. Just verified that, with your pattern, the message-loop itself is running on a TP thread by checking `Thread.CurrentThread.IsThreadPoolThread` in `OnLoad` on the auxiliary form.
Ani
@Ani: Nice, thanks for the suggestion. It's been so drilled into my head, "Don't create new threads -- use threadpool threads!" that sometimes I forget it can actually be appropriate to do so.
Dan Tao
I would recommend starting a dedicated thread and not hosting any windows/message loops on the thread pool. You might run into all kind of weird behavior if you don't have STA (worker threads are in MTA).
liggett78
Yep, I'd totally go with an actual dedicated thread here. `ShowDialog` starts a message loop so it is probably okay to do that instead of `Application.Run`. But, either way is fine.
Brian Gideon