views:

435

answers:

3

(I have a workaround for this problem, but it's not the first time I've been bitten, so I'm trying to understand exactly what's going on.)

  • From my application, I ShowDialog a form.
  • On the form is a button, which when clicked calls code on another (non-Gui) thread.
  • The non-GUI thread sends back statuses (Pushed then Released) via a Control.Invoke
  • When the form sees the Pushed, it invokes form.Hide()
  • When the form sees the Released, it changes the appearance of the button.

What happens is that sometimes, but not everytime, the non-Gui thread gets 'stuck' trying to send the Released. No exceptions, the Gui carries on 'working', but no further communication with the non-Gui thread is possible, in either direction.

The (simplified) callstack for the thread looks like this:

System.Threading.WaitHandle.WaitOne()
(...)
System.Windows.Forms.Control.WaitForWaitHandle()
(...)
System.Windows.Forms.Control.Invoke()
(...)
GuiCode.OnStatusChanged()
(...)
NonGuiCode.SetStatus()

The problem goes away if I replace ShowDialog with Show, but - interestingly - it gets better (happens less often) but doesn't completely go away if I comment out the code that does the Hide on Pushed.

Update

Thanks to nobugz, I've discovered deadlock (I'd only ever met it in databases before)! Apparently replacing Control.Invoke with Control.BeginInvoke solves this problem (the status event still gets 'stuck' sometimes, but it doesn't block all following communications).

+2  A: 

Clearly, you are battling deadlock. That's always around the corner when you use Control.Invoke() instead of BeginInvoke(). It isn't clear to me what trips the deadlock from your post. One red flag is using Hide() on a form that you displayed with ShowDialog(). That normally closes the dialog.

Best thing to do is to debug it. Wait until the deadlock occurs, then use Debug + Break All. Use Debug + Windows + Threads and switch to the UI thread. Look at the Call Stack. If it is not pumping the message loop (Application.Run()) but is stuck somewhere (like the wait handle you appear to be using) then deadlock is the result.

Hans Passant
Yup, I'm trying to recycle the form. Having said that, Closing it, or just setting DialogResult, still has the same effect. Now I need to go and read up on the difference between Invoke and BeginInvoke...
Benjol
A: 

I just encountered what I think is this.

From a GUI thread, invoke onto another GUI thread, and have that thread do ShowDialog. If the user's GUI preferences change (e.g. background rotater), deadlock.

Joshua
+2  A: 

To handle the Control.Invoke() call, the GUI thread has to pump a Windows message, but ShowDialog() is a blocking call, so it can't do that until ShowDialog() returns.

Control.Invoke() is also blocking, and the thread calling it has to wait until the GUI thread picks up the message and handles it to continue. If the code that includes the Control.Invoke() is what enables dismissal of the dialog, then bingo, there's your deadlock.

This is all a bit tricky, because deadlock detectors like SosEx's dlk command can't detect the problem from a dump or WinDgb session - the 'lock' involved in the GUI thread's processing of Control.Invoke() is 'implied', not an actual WaitHandle.

McKenzieG1