Проблемы, связанные с отображением MessageBox из не-GUI потоков - PullRequest
4 голосов
/ 08 апреля 2010

Я работаю над приложением Win.Forms с большим объемом данных, в котором обнаружилось странное поведение. Приложение имеет отдельные потоки ввода / вывода, получающие обновления через асинхронные веб-запросы. который он затем отправляет в основной поток / GUI-поток для обработки и обновления хранилищ данных в масштабе приложения (которые, в свою очередь, могут быть привязаны к данным для различных GUI-элементов и т. д.). Серверу на другом конце веб-запросов требуются периодические запросы или время ожидания сеанса.

Я пробовал несколько попыток решения проблем с потоками и т. Д. И наблюдал следующее поведение:

  1. Если я использую Control.Invoke для отправки обновлений из потоков ввода-вывода в основной поток, и это обновление приводит к тому, что MessageBox показывается, когда основной поток сообщений в основной форме останавливается, пока пользователь не нажмет кнопку ОК кнопка. Это также блокирует продолжение потока ввода-вывода, что в конечном итоге приводит к тайм-аутам на сервере.

  2. Если я использую Control.BeginInvoke для отправки обновлений из потоков ввода-вывода в основной поток, насос сообщений основной формы не останавливается, но если обработка обновления приводит к отображению окна сообщения, обработка остальной части этого обновления останавливается до тех пор, пока пользователь не нажмет ok. Поскольку потоки ввода-вывода продолжают работать, а насос сообщений продолжает обрабатывать сообщения, несколько BeginInvoke для обновлений могут быть вызваны до того, как завершится процесс с сообщением. Это приводит к непоследовательным обновлениям, что недопустимо.

  3. Потоки ввода-вывода добавляют обновления в очередь блокировки (очень похоже на Создание очереди блокировки в .NET? ). GUI-поток использует Forms.Timer, который периодически применяет все обновления в очереди блокировки. Это решение решает как проблему блокировки потоков ввода-вывода, так и последовательность обновлений, т. Е. Следующее обновление никогда не будет запущено, пока не будет завершено предыдущее. Тем не менее, существует небольшая стоимость производительности, а также введение задержки при показе обновлений, что недопустимо в долгосрочной перспективе. Я хотел бы, чтобы обработка обновлений в основном потоке была основана на событиях, а не на опросе.

Итак, на мой вопрос. Как мне сделать это, чтобы:

  1. избегать блокировки потоков ввода / вывода
  2. гарантия завершения обновлений
  3. поддерживать основной насос сообщений во время отображения окна сообщения в результате обновления.

Обновление: см. Решение ниже

Ответы [ 4 ]

5 голосов
/ 08 апреля 2010

MessageBox сам прокачивает цикл сообщений.Это, конечно, не будет цикл сообщений Windows Forms.Все работает как обычно, но за исключением отправки запросов на вызов делегатов, отправленных Control.BeginInvoke ().Это может делать только цикл сообщений Windows Forms.

Это происходит, когда в потоке пользовательского интерфейса выполняется вызов MessageBox.Show ().Но не тогда, когда это делается в рабочем потоке, очереди сообщений являются свойством каждого потока.Если вы можете получить вызов Show для делегирования работнику, вы, вероятно, решите свою проблему.

Решение вопросов:

  1. Вы действительно хотите обратное: работниктемы должны блокировать.Неблокирование может вызвать серьезные проблемы, очередь отправки BeginInvoke будет заполняться без границ.Одним из возможных приемов является подсчет количества вызовов BeginInvoke, обратный отсчет в цели делегата.Используйте класс Interlocked.

  2. Порядок выполнения целей BeginInvoke гарантирован.Настоящая проблема, вероятно, связана с тем, что рабочие потоки не синхронизируются.

  3. Отображение окна сообщения в потоке.

1 голос
/ 08 апреля 2010

Не используйте Forms.Timer для применения обновлений из очереди, но используйте другой поток для этого.Этот поток постоянно следит за очередью и (возможно) сообщает GUI, когда обновлять себя новыми данными (через BeginInvoke). MessageBox может отображаться из этого потока чтения очереди - он не должен быть потоком GUI.


Редактировать: Потребитель очереди может вызвать Control.Invoke для отображения messageBox, чтобы обойти проблему z-порядка

1 голос
/ 08 апреля 2010

Итак, у вас есть сложная цепочка сбора и обработки данных, которую вы хотите продолжать работать, но затем вставляете туда MessageBox.Ничто в Threading + Invoke не изменит того факта, что MessageBox является модальным и что вам придется ждать его закрытия, что делает всю цепочку зависимой от того, что пользователь что-то щелкнет.

Итак, избавьтесь от MessageBox, хотя бы по основному пути.Если сегмент обработки требует вмешательства пользователя, тогда этот сегмент должен находиться в отдельном потоке.

0 голосов
/ 12 апреля 2010

Вот решение, которое я выбрал:

  • Поток ввода-вывода помещает все обновления в потокобезопасную очередь / очередь блокировки.
  • Раздельное вращение рабочего потока, бесконечное удаление обновлений и последующее их начало в GUI-потоке.
  • Отображение MessageBox в GUI-потоке в ответ на обновления теперь выполняется с помощью BeginInvoke.

Это решение имеет следующие преимущества по сравнению с предыдущим (описано в 3. выше с использованием опроса для обновлений GUI):

  1. Обновление GUI по запросу, а не опрос. Это дает (теоретически) лучшую производительность и меньшую задержку.
  2. Окно сообщений не блокирует ни обновления GUI, ни ввод-вывод.

Обновление: похоже, что обновления GUI все еще заблокированы, пока отображается окно сообщения с использованием этого решения. Обновится, когда это будет исправлено.

Обновление 2: обновлено с исправлением для рабочего потока, изменив Invoke на BeginInvoke.

...