与从非 GUI 线程显示 MessageBox 相关的问题

发布于 2024-08-28 06:29:11 字数 996 浏览 11 评论 0原文

我正在开发一个数据密集型的 Win.Forms 应用程序,我发现了一些奇怪的行为。该应用程序具有单独的 I/O 线程,通过异步 Web 请求接收更新 然后将其发送到主/GUI 线程以处理和更新应用程序范围的数据存储(反过来可能将数据绑定到各种 GUI 元素等)。 Web 请求另一端的服务器需要定期请求,否则会话就会超时。

我已经尝试了几种处理线程问题等的解决方案,并且观察到以下行为:

  1. 如果我使用 Control.Invoke 将更新从 I/O 线程发送到主线程此更新会导致显示一个 MessageBox,主窗体的消息泵将停止,直到用户单击“确定”按钮。这也会阻止 I/O 线程继续运行,最终导致服务器超时。

  2. 如果我使用 Control.BeginInvoke 将更新从 I/O 线程发送到主线程,则主窗体的消息泵不会停止,但如果更新的处理导致显示消息框,该更新的其余部分的处理将停止,直到用户单击“确定”。由于 I/O 线程持续运行并且消息泵持续处理消息,因此在消息框完成之前可能会调用多个 BeginInvoke 进行更新。这会导致不可接受的无序更新。

  3. I/O 线程将更新添加到阻塞队列(非常类似于 在 .NET 中创建阻塞队列)。 GUI线程使用Forms.Timer定期应用阻塞队列中的所有更新。该解决方案解决了阻塞 I/O 线程和更新顺序的问题,即下一个更新将永远不会开始,直到上一个更新完成。然而,这会带来很小的性能成本,并且在显示更新时引入延迟,从长远来看这是不可接受的。我希望主线程中的更新处理是事件驱动的,而不是轮询。

那么对于我的问题。我应该如何做到这一点:

  1. 避免阻塞 I/O 线程
  2. 保证更新按顺序完成
  3. 保持主消息泵运行,同时显示更新结果的消息框。

更新:请参阅下面的解决方案

I'm working on a heavily data-bound Win.Forms application where I've found some strange behavior. The app has separate I/O threads receiving updates through asynchronous web-requests
which it then sends to the main/GUI thread for processing and updating of application-wide data-stores (which in turn may be data-bound to various GUI-elements, etc.). The server at the other end of the web-requests requires periodic requests or the session times out.

I've gone through several attempted solutions of dealing with thread-issues etc. and I've observed the following behavior:

  1. If I use Control.Invoke for sending updates from I/O-thread(s) to main-thread and this update causes a MessageBox to be shown the main form's message pump stops until the user clicks the ok-button. This also blocks the I/O-thread from continuing eventually leading to timeouts on the server.

  2. If I use Control.BeginInvoke for sending updates from I/O-thread(s) to main-thread the main form's message pump does not stop, but if the processing of an update leads to a messagebox being shown, the processing of the rest of that update is halted until the user clicks ok. Since the I/O-threads keep running and the message pump keeps processing messages several BeginInvoke's for updates may be called before the one with the message box is finished. This leads to out-of-sequence updates which is unacceptable.

  3. I/O-threads add updates to a blocking queue (very similar to Creating a blocking Queue<T> in .NET?). GUI-thread uses a Forms.Timer that periodically applies all updates in the blocking queue. This solution solves both the problem of blocking I/O threads and sequentiality of updates i.e. next update will be never be started until previous is finished. However, there is a small performance cost as well as introducing a latency in showing updates that is unacceptable in the long run. I would like update-processing in the main-thread to be event-driven rather than polling.

So to my question. How should I do this to:

  1. avoid blocking the I/O-threads
  2. guarantee that updates are finished in-sequence
  3. keep the main message pump running while showing a message box as a result of an update.

Update: See solution below

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

远山浅 2024-09-04 06:29:11

MessageBox 本身泵送消息循环。那当然不会是 Windows 窗体消息循环。一切都正常运行,但减去 Control.BeginInvoke() 发布的委托调用请求的调度。只有 Windows 窗体消息循环可以做到这一点。

当在 UI 线程上调用 MessageBox.Show() 时,会发生这种情况。但当消息队列在工作线程上创建时,情况就不同了,消息队列是每个线程的属性。如果您可以将 Show 调用委托给工作人员,则可能会解决您的问题。

解决您的问题:

  1. 您确实想要相反的结果:工作线程应该阻塞。不阻塞会导致重大问题,BeginInvoke 调度队列将无限地填满。一种可能的技巧是计算 BeginInvoke 调用的数量,在委托目标中倒计时。使用 Interlocked 类。

  2. BeginInvoke 目标的执行顺序是有保证的。真正的问题可能与工作线程不同步有关。

  3. 在线程上显示消息框。

MessageBox itself pumps a message loop. That of course won't be the Windows Forms message loop. Everything runs as normal, but minus the dispatching of delegate invocation requests posted by Control.BeginInvoke(). Only the Windows Forms message loop can do that.

This happens when the MessageBox.Show() call is made on the UI thread. But not when it is made on a worker thread, message queues are a per-thread property. If you can get the Show call to be delegated to a worker, you probably solve your problem.

Addressing your questions:

  1. You really want the opposite: the worker threads should block. Not blocking can cause major problems, the BeginInvoke dispatch queue will fill up without bounds. One possible trick is to count the number of BeginInvoke calls, count down in the delegate target. Use the Interlocked class.

  2. The execution order of BeginInvoke targets is guaranteed. The real problem is probably related to having worker threads getting out of sync.

  3. Show the message box on a thread.

爱的故事 2024-09-04 06:29:11

因此,您有一个复杂的数据采集和处理链,您希望继续运行它,但随后您在其中插入了一个 MessageBox。 Threading+Invoke 中的任何内容都不会改变 MessageBox 是模态的这一事实,并且您必须等待它关闭,从而使整个链依赖于用户单击某些内容。

因此,至少在主路径中删除 MessageBox。如果处理的某个部分确实需要用户干预,则该部分必须位于单独的线程上。

So you've got a complicated data-acquisition and processing chain that you want to keep running but then you insert a MessageBox in there. Nothing in the Threading+Invoke will change the fact that a MessageBox is Modal and that you have to wait for it to close, making the whole chain dependent on the User to click something.

So, get rid of the MessageBox, at least in the main path. If a segment of the processing does require user intervention then that segment must be on a separate thread.

゛时过境迁 2024-09-04 06:29:11

不要使用 Forms.Timer 应用队列中的更新,而应使用另一个线程来执行此操作。该线程持续监视队列并(也许)告诉 GUI 何时用新数据刷新自身(通过 BeginInvoke) MessageBox 可以从此队列读取器线程显示 - 不必是 GUI 线程。


编辑:队列使用者可以调用 Control.Invoke 来显示 messageBox 以解决 z 顺序问题

Don't use Forms.Timer to apply updates from the queue but use another thread to do it. This thread continually monitors the queue and (maybe) tells the GUI when to refresh itself with new data (via BeginInvoke) The MessageBox can be shown from this queue reader thread - does not have to be GUI thread.


Edit: The queue consumer can call Control.Invoke to display the messageBox to get around z-order issue

戴着白色围巾的女孩 2024-09-04 06:29:11

这是我最终得到的解决方案:

  • I/O 线程将所有更新放在线程安全/锁定队列上。
  • 单独的工作线程无限地旋转,使更新出队,然后开始将它们调用到 GUI 线程中。
  • 现在,通过 BeginInvoke 在 GUI 线程中显示 MessageBox 以响应更新。

与之前的解决方案(在上面的 3. 中描述的使用轮询 GUI 更新)相比,该解决方案具有以下优点:

  1. GUI 更新的事件驱动更新而不是轮询。这提供了(理论上)更好的性能和更少的延迟。
  2. GUI 更新和 I/O 都不会被消息框锁定。

更新:当使用此解决方案显示消息框时,GUI 更新似乎仍然被锁定。修复此问题后将更新。

更新 2:通过将 Invoke 更改为 BeginInvoke 来更新修复工作线程。

Here is the solution I ended up with:

  • I/O thread puts all updates on a thread-safe/locking queue.
  • Separate worker-thread spins endlessly Dequeing updates and then BeginInvoke'ing them into the GUI-thread.
  • Display of MessageBox in GUI-thread in response to updates is now done with BeginInvoke.

This solution has the following benefits compared to the previous (described in 3. above using polling for GUI-updates):

  1. Event-driven update of GUI rather than polling. This gives both a (in theory) better performance and less latency.
  2. Neither GUI-updates nor I/O are locked by the Message Box.

Update: it seems that GUI-updates are still locked while a messagebox is shown using this solution. Will update when this is fixed.

Update 2: updated with fix for the worker-thread by changing Invoke to BeginInvoke.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文