Control.Invoke получает "застрял" в скрытом ShowDialog

(У меня есть обходной путь для этой проблемы, но это не первый раз, когда я был укушен, поэтому я пытаюсь понять, что происходит.)

  • Из моего приложения я ShowDialog форма.
  • В форме есть кнопка, которая при нажатии на код вызывает другой (не-Gui) поток.
  • Нить без GUI отправляет состояния статуса (Pushed then Released) через Control.Invoke
  • Когда форма видит Pushed, она вызывает form.Hide()
  • Когда форма видит Released, она меняет внешний вид кнопки.

Случается, что иногда, но не каждый раз, поток не-Gui "застревает", пытаясь отправить Released. Никаких исключений, Gui продолжает "работать", но никакое дальнейшее общение с нитью Gui не возможно в любом направлении.

Столбец (упрощенный) для потока выглядит следующим образом:

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

Проблема исчезает, если я заменяю ShowDialog на Show, но - интересно - он становится лучше (случается реже), но не полностью исчезает, если я прокомментирую код, который делает Hide on Pushed.

Обновление

Благодаря nobugz я обнаружил тупик (раньше я только встречался в базах данных)! По-видимому, замена Control.Invoke на Control.BeginInvoke решает эту проблему (событие состояния по-прежнему иногда "застревает", но не блокирует все последующие сообщения).

Ответ 1

Ясно, что вы сражаетесь с тупиком. Это всегда за углом, когда вы используете Control.Invoke() вместо BeginInvoke(). Мне непонятно, что из тупика выйдет из тупика. Один красный флаг использует Hide() в форме, отображаемой с помощью ShowDialog(). Это обычно закрывает диалог.

Лучше всего это отладить его. Подождите, пока произойдет тупик, затем используйте Debug + Break All. Используйте Debug + Windows + Threads и переключитесь на поток пользовательского интерфейса. Посмотрите на стек вызовов. Если он не перекачивает контур сообщения (Application.Run()), а застревает где-то (например, дескриптор ожидания, который вы используете), то результатом является тупик.

Ответ 2

Чтобы обрабатывать вызов Control.Invoke(), поток GUI должен передавать сообщение Windows, но ShowDialog() является блокирующим вызовом, поэтому он не может этого сделать до тех пор, пока ShowDialog() не вернется.

Control.Invoke() также блокирует, и поток, вызывающий его, должен ждать, пока поток GUI не подберет сообщение и не продолжит его. Если код, включающий Control.Invoke(), позволяет отключить диалог, тогда bingo, там ваш тупик.

Все это немного сложно, потому что тупиковые детекторы, такие как SosEx dlk, не могут обнаружить проблему из сеанса дампа или WinDgb - блокировка, участвующая в обработке потоков GUI Control.Invoke(), подразумевается ', а не фактический WaitHandle.

Ответ 3

Я просто столкнулся с тем, что я думаю.

Из потока GUI вызовите другой поток GUI и создайте этот поток ShowDialog. Если пользовательские настройки GUI изменяются (например, ротатор фона), тупик.