Проблемы, связанные с отображением MessageBox из потоков без графического интерфейса пользователя
Я работаю над сильно привязанной к данным победой.Формы приложения, где я обнаружил какое-то странное поведение. Приложение имеет отдельные потоки ввода-вывода, получающие обновления через асинхронные веб-запросы который он затем отправляет в основной поток / GUI для обработки и обновления хранилищ данных всего приложения (которые, в свою очередь, могут быть привязаны к различным GUI-элементам и т. д.). Сервер на другом конце веб-запросов требует периодических запросов или тайм-аута сеанса.
Я прошел через несколько попытки решения проблем с потоками и т. д. и я наблюдал следующее поведение:
-
Если я использую контроль.Вызов для отправки обновлений из потока ввода-вывода в основной поток, и это обновление вызывает MessageBox, чтобы показать сообщение основной формы насос останавливается, пока пользователь не нажмет кнопку ok. Это также блокирует поток ввода-вывода от продолжения в конечном итоге приводит к тайм-аутам на сервере.
-
Если я использую контроль.BeginInvoke для отправки обновлений из потока ввода-вывода(потоков) для main-thread насос сообщений основной формы не останавливается , но если обработка обновления приводит к отображению messagebox, обработка остальной части этого обновления приостанавливается до тех пор, пока пользователь не нажмет ok. Поскольку потоки ввода-вывода продолжают работать, а насос сообщений продолжает обрабатывать сообщения, несколько BeginInvoke для обновлений могут быть вызваны до того, как завершится тот, в котором находится окно сообщения. Это приводит к внесистемным обновлениям, что недопустимо.
-
I / O-threads add обновление блокирующей очереди (очень похоже на создание блокирующей очереди в .NET? ). GUI-поток использует формы.Таймер, который периодически применяет все обновления в очереди блокировки. Это решение решает как проблему блокировки потоков ввода-вывода, так и последовательность обновлений, т. е. следующее обновление никогда не будет запущено, пока предыдущее не будет завершено. Тем не менее, существует небольшая стоимость производительности, а также введение задержки в показе обновлений, что неприемлемо в долгосрочной перспективе. Я хотел бы обработка обновлений в главном потоке должна быть управляемой событиями, а не опросом.
Итак, на мой вопрос. Как я должен это сделать, чтобы:
- Избегайте блокировки потоков ввода-вывода
- гарантировать, что обновления будут завершены в последовательности
- Держите основной насос сообщений запущенным, показывая окно сообщения в результате обновления.
Обновление: смотрите Решение ниже
4 ответа:
MessageBox сам перекачивает цикл сообщений. Это, конечно, не будет цикл сообщений Windows Forms. Все работает как обычно, но за вычетом отправки запросов на вызов делегатов, отправленных управлением.BeginInvoke (). Это может сделать только цикл сообщений Windows Forms.
Это происходит, когда MessageBox.Вызов Show () выполняется в потоке пользовательского интерфейса. Но не тогда, когда это делается в рабочем потоке, очереди сообщений являются свойством для каждого потока. Если вы можете получить шоу-вызов, который будет делегирован a рабочий, вы, вероятно, решите свою проблему.
Отвечая на ваши вопросы:
Вы действительно хотите обратного: рабочие потоки должны блокировать. Отсутствие блокировки может вызвать серьезные проблемы, очередь отправки BeginInvoke будет заполняться без ограничений. Одним из возможных трюков является подсчет количества вызовов BeginInvoke, обратный отсчет в целевом делегате. Используйте блокируемый класс.
Порядок выполнения целей BeginInvoke гарантирован. Настоящая проблема заключается в следующем вероятно, это связано с тем, что рабочие потоки выходят из синхронизации.
Показать окно сообщения в потоке.
Итак, у вас есть сложная цепочка сбора и обработки данных, которую вы хотите продолжать работать, но затем вы вставляете туда MessageBox. Ничто в Threading + Invoke не изменит тот факт, что MessageBox является модальным и что вы должны ждать его закрытия, что делает всю цепочку зависимой от пользователя, чтобы щелкнуть что-то.
Итак, избавьтесь от MessageBox, по крайней мере, на главном пути. Если сегмент обработки действительно требует вмешательства пользователя, то этот сегмент должен находиться на отдельный поток.
Не используйте формы.Таймер, чтобы применить обновления из очереди, но использовать другой поток, чтобы сделать это. Этот поток постоянно отслеживает очередь и (возможно) сообщает GUI, когда обновить себя новыми данными (через BeginInvoke) MessageBox может быть показан из этого потока чтения очереди - не обязательно быть потоком GUI.
Edit: потребитель очереди может вызвать управление.Вызовите для отображения messageBox, чтобы обойти проблему z-order
Вот решение, которое я получил:
- поток ввода-вывода помещает все обновления в потокобезопасную / блокирующую очередь.
- отдельный рабочий поток бесконечно запускает обновления, а затем начинает вызывать их в GUI-поток.
- отображение MessageBox в GUI-потоке в ответ на обновления теперь выполняется с помощью BeginInvoke.
Это решение имеет следующие преимущества по сравнению с предыдущим (описано в 3. выше использование опроса для GUI-updates):
- событийное обновление графического интерфейса, а не опрос. Это дает как (в теории) лучшую производительность, так и меньшую задержку.
- ни обновления GUI, ни ввод-вывод не блокируются окном сообщения.
Update: похоже, что GUI-обновления все еще заблокированы, пока messagebox отображается с помощью этого решения. Обновится, когда это будет исправлено.
Update 2: обновление с исправлением для рабочего потока путем изменения Invoke на BeginInvoke.