Control.Invoke在隐藏的ShowDialog中被“卡住”

时间:2010-01-13 10:33:24

标签: c# .net multithreading invoke

(我有一个解决这个问题的方法,但这不是我第一次被咬,所以我试图准确理解发生了什么。)

  • 从我的申请表中,我ShowDialog表格。
  • 在表单上是一个按钮,单击该按钮会调用另一个(非Gui)线程上的代码。
  • 非GUI线程通过Control.Invoke发送回状态(Pushed然后Released
  • 当表单看到Pushed时,会调用form.Hide()
  • 当表单看到Released时,它会更改按钮的外观。

有时,但不是每次,非Gui线程试图发送Released时都会“卡住”。没有例外,Gui继续“工作”,但是在任何一个方向上都不可能与非Gui线程进行进一步的沟通。

线程的(简化)callstack如下所示:

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

如果我用ShowDialog替换Show,问题就会消失,但是 - 有趣的是 - 它变得更好(发生的次数减少)但是如果我注释掉代码执行的话,它就不会完全消失Hide上的Pushed

更新

感谢nobugz,我发现了死锁(我之前只在数据库中遇到过它)!显然用Control.BeginInvoke替换Control.Invoke解决了这个问题(状态事件有时会“卡住”,但它不会阻止所有后续通信。)

3 个答案:

答案 0 :(得分:4)

要处理Control.Invoke()调用,GUI线程必须提取Windows消息,但ShowDialog()是阻塞调用,因此在ShowDialog()返回之前不能执行此操作。

Control.Invoke()也是阻塞的,调用它的线程必须等到GUI线程获取消息并处理它才能继续。如果包含Control.Invoke()的代码能够解除对话,那么 bingo 就会导致您的死锁。

这有点棘手,因为像SosEx的dlk命令这样的死锁检测器无法检测转储或WinDgb会话中的问题 - GUI线程处理Control.Invoke()时涉及的“锁定”是“暗示”,而不是实际的WaitHandle

答案 1 :(得分:3)

显然,你正在与僵局作斗争。当你使用Control.Invoke()而不是BeginInvoke()时,这总是蠢蠢欲动。我不清楚从你的帖子中解决僵局的原因。一个红旗在您使用ShowDialog()显示的表单上使用Hide()。这通常会关闭对话框。

最好的办法是调试它。等到发生死锁,然后使用Debug + Break All。使用Debug + Windows + Threads并切换到UI线程。查看调用堆栈。如果它没有引发消息循环(Application.Run())但是卡在某处(比如你似乎正在使用的等待句柄),那么结果就是死锁。

答案 2 :(得分:0)

我刚刚遇到了我的想法。

从GUI线程调用另一个GUI线程,让该线程执行ShowDialog。如果用户的GUI首选项发生变化(例如背景旋转器),则死锁。