views:

257

answers:

4

In my WinForms application, I need to pop up a little custom dialog that stays on the screen for X amount of seconds and then disappears. So I use a System.Threading.Timer to invoke the _dialog.Close() method once the appropriate amount of time has elapsed. This of course means that I have to do the whole "if InvokeRequired BeginInvoke" dance which isn't really a problem.

What is a problem however is that my main thread might be off doing god knows what by the time the BeginInvoke is called. It might not get around to closing the window for quite a while. I don't need the window to close at a millisecond's notice, but within a second or so is really necessary.

So my question is how does BeginInvoke actually work itself into the main thread and how can I get around this odd limitation?

+4  A: 

UPDATE: The conclusion would seem to be that utilising ['BackgroundWorker](http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx) along with a System.Windows.Forms.Timer would be the best approach.

Best to use System.Windows.Forms.Timer for this purpose - this is precisely the sort of application it was designed for. Add one to the pop up form and start it as soon as the form is shown, then hide the form on the Tick event. This solution won't give you any threading issues because the timer runs purely on the UI thread.

Edit: If you want to move the logic outside of your popup form, then I recommend you just create an overload for the Show method within the form code that takes a timespan for its parameter and does the job of setting the Timers's interval and starting it.

Edit 2: If you're main (UI) thread is doing too much work and therefore blocking the message pump and not allowing the timer to fire, then it's the design that's the issue I'm afraid. Your UI thread should never be blocking for more than a fraction of a second. If you need to do serious work, do it in the background using a worker thread. In this case, because you are using WinForms, BackgroundWorker is probably the best option.

Noldorin
But then the form itself must know about when its going to close! That's not a solution, I am trying to keep as much logic as possible outside of the winform.
George Mauer
This won't solve the issue of the UI thread being otherwise occupied.
Adam Robinson
@George: Post updated.
Noldorin
@Adam: The UI thread shouldn't be doing too much work. This is what worker threads (or thread pools) are for, and it's really bad design practice to do otherwise.
Noldorin
Regarding Show method with a timespan arg. That's kind of silly since my application also has windows that are closed from code when some condition is met explicitly. Its far more reusable to just make that condition a timer elapsing. That being said, I get what you're saying with the UI thread blocking. Shouldn't be too much of a problem to fix.
George Mauer
Ok, so I probably misunderstood you regarding the popup. I thought you just wanted to close it after a specified time period (though that wouldn't stop you from doing so explicitly anyway). Glad you see the solution now...
Noldorin
+1  A: 

Invoke just places the delegate into the message queue of the thread you want to invoke it on. You could use the Dispatcher class to insert the delegate with a high priority, but there is no gurante that this will meet you timing constraints if the thread is doing a lot of work.

But this might be an indication that you are doing to much work on the user interface thread. Not responding for a second is a pain to a user. So you might think about moving some work out of the user interface thread.

Daniel Brückner
Absolutely, I've been trying to do this but its far from easy to actually do this. Is there a tutorial or article on best practices for this?
George Mauer
Tutorial on what? Factoring the work out of the GUI or using the Dispatcher class?
Daniel Brückner
For how to have the actual work of your application not run on the UI thread. I am using an MVP architecture and short of wrapping each presenter call in a BackgroundWorker (which is annoying but a possibility) I see no other way to do this.
George Mauer
+2  A: 

Create a dedicated thread and use Application.Run to create and show your form. This will start up a message pump on the second thread which is independent of the main thread. This can then close exactly when you want it, even if the main thread is blocked for any reason.

Invoke and BeginInvoke do get into the main thread by using a window message posted into that thread, waiting for it to be processed. Therefore, if the message pump of the main thread is not processing messages (e.g. busy), it will have to wait. You can mitigate this factor by calling Application.DoEvents() when doing time-consuming operations in the main thread, but that's not really a solution to the problem.

Edit: Sample from some splash screen code (the form requires no special code or logic):

 private void ShowSplashInSeparateMessageQueue() {
  Thread splash = new Thread(ShowSplashForm);
  splash.IsBackground = true;
  splash.Start();
 }

 private void ShowSplashForm() { // runs in a separate thread and message queue
  using (SplashForm splashForm = new SplashForm()) {
   splashForm.Load += AddDestroyTimer;
   Application.Run(splashForm);
  }
 }

 private void AddDestroyTimer(object sender, EventArgs e) {
  Form form = (Form)sender;
  System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer(form.Container);
  timer.Tick += delegate { form.Close(); };
  timer.Interval = 5000;
  timer.Start();
    }
Lucero
+1. Just what I was about to post!
Adam Robinson
Can you please explain further? Im intrigued.
George Mauer
What would you like as further explanation? I use this "all the time", for instance for splash screens or to show unhandled exception dialogs. This allows the main thread to be locked while still giving you a responsive UI for the dedicated dialog on its own thread.
Lucero
I am not sure how you are saying creating a thread, creating the dialog (which is currently handled by an IoC container but I guess I can figure something out), starting the thread, showing the dialog, calling Application.Run and closing the thread fit together. Can you give a quick code example?
George Mauer
+5  A: 

If your UI thread is busy for many seconds at a time, then:

  • You won't be able to close a window associated with that UI thread, without peppering your code with Application.DoEvents calls (a bad idea)
  • Your whole UI will be unresponsive during this time. The user won't be able to move any of the application's windows, and if the user drags other windows over the top of it and off again, you'll end up with an ugly mess while the UI waits to repaint itself.

Certainly use a System.Windows.Forms.Timer instead of a System.Threading.Timer for simplicity, but more urgently, look at fixing your code to avoid having such a busy UI thread.

Jon Skeet
Yeah, like I said in the comments above, it is possible that the business is from messageboxes used for testing and that when I remove them there will be no problem. As for using the Forms Timer that's a pretty bad break of SoC in my opinion. My dialog shouldn't know when or who is going to be closing it, that call should be from an external source.
George Mauer
The dialog doesn't need to know - it just needs to stipulate that it's up to the caller to make sure they're on the right thread before calling Close. Sounds entirely reasonable to me. You could even use a System.Timers.Timer and set the Timer.SynchronizingObject if you want. You're only making the same demand that all other GUI elements make.
Jon Skeet