2010-04-08 52 views
4

我正在处理一个严重的数据绑定的Win.Forms应用程序,我发现了一些奇怪的行为。该应用程序具有单独的I/O线程,通过异步web请求接收更新,然后将其发送到主/ GUI线程,以处理和更新应用程序范围的数据存储(这又可以将数据绑定到各种GUI-元素等)。 Web请求另一端的服务器需要定期请求或会话超时。与从非GUI线程显示MessageBox相关的问题

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

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

  2. 如果我使用Control.BeginInvoke从I/O线程(或多个)发送更新主线程的主窗体的消息泵不停止,但如果更新的处理导致了一个消息是如图所示,该更新的其余部分的处理将暂停,直到用户单击确定。由于I/O线程继续运行,并且消息泵继续处理消息,因此可以在消息框完成之前调用几个用于更新的BeginInvoke。这导致无法接受的失序更新。

  3. I/O线程将更新添加到阻塞队列(非常类似于Creating a blocking Queue<T> in .NET?)。 GUI线程使用Forms.Timer定期应用阻塞队列中的所有更新。该解决方案既解决了阻塞I/O线程的问题,又解决了更新的顺序性问题,即直到完成前一个更新才会开始。但是,性能成本很低,并且在显示更新时引入延迟,这在长期内是不可接受的。我希望主线程中的更新处理是事件驱动的而不是轮询。

所以对我的问题。我应该怎么做这:

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

更新:请参见下面

+0

问题:必须在用户单击messageBox上的“确定”后才更新GUI,或者只能通知他们已发生更新,例如通过多行文本框中的消息? – Asher 2010-04-08 10:19:56

+0

它必须是一个弹出窗口,但我不确定它是否需要模态。我可以创建自己的非模态弹出窗口。 – 2010-04-08 11:14:41

回答

0

这里是我结束了与解决方案:

  • I/O线程将所有更新放在线程安全/锁定队列中。
  • 独立的工作线程无休止地分离更新,然后开始将它们调入GUI线程。
  • 在GUI-thread中响应更新显示MessageBox现在用BeginInvoke完成。相比之前的(在3以上使用GUI的更新轮询描述)

此解决方案具有以下优点:GUI而不是轮询的

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

更新:似乎使用此解决方案显示消息框时GUI-updates仍然被锁定。这会在更正时更新。

更新2:通过将Invoke更改为BeginInvoke,更新了针对工作线程的修复。

+0

从你的问题中,你听起来像是队列的处理只能在用户点击确定后继续。或者我就是这样读的 – Asher 2010-04-12 14:03:05

+0

抱歉误会。情况并非如此,但重要的是该消息是模态的并且用户及时得到消息。 – 2010-04-12 14:47:34

1

解决方案,使你有一个复杂的数据采集和处理的过程要保持运行,但随后你在里面插入一个消息。线程+调用中的任何内容都不会改变MessageBox是Modal的事实,并且您必须等待它关闭,从而使整个链依赖于用户点击某个内容。

所以,摆脱MessageBox,至少在主要路径。如果处理的一部分确实需要用户干预,那么该部分必须位于单独的线程上。

+0

消息框由监听器远远显示在数据流中,用于监听来自远程交易系统的错误消息,并且必须是弹出窗口。一个可能的解决方案可能是做MessageBox。使用BeginInvoke显示它自己,它不需要与接收更新同步。 让我感到困惑的是,当调用(主要消息泵停止)时,MessageBox形式的行为与源自主线程内的事件或使用BeginInvoke(即使在显示MessageBox时消息泵继续处理其他事件)时的行为有所不同。 – 2010-04-08 11:20:05

+0

消息泵始终保持运行模式弹出,但事件过滤的逻辑从根本上改变。但请注意,您可以Control.Invoke一个_function_并等待结果(SendMessage)。这与BeginInvoke有所不同,它使用PostMessage。 – 2010-04-08 11:36:56

+0

并显示另一个线程上的MessageBox需要第二个Application.Run。使用自己的窗体并使用Show而不是ShowDialog可能更容易。 – 2010-04-08 11:38:31

5

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

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

解决您的问题:

  1. 你真正想要的正好相反:工作线程应该阻止。不阻塞会导致严重问题,BeginInvoke派发队列将无限制地填满。一个可能的技巧是计算BeginInvoke调用的数量,在委托目标中倒数。使用Interlocked类。

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

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

+0

1.在这种情况下,问题是工作线程是I/O线程,并阻止它(最终)超时服务器上的会话。此外,所有收到的更新在这个应用程序中都非常重要,所以它们必须以某种方式排队。 2.您确认BeginInvoke的执行顺序是有保证的,但我不认为它们是原子的。有关更详细的解释:http://www.codeproject.com/KB/cs/begininvoke.aspx 3.我认为MessageBox总是应该在其他GUI的主/ GUI线程中使用 – 2010-04-08 12:18:34

+0

限制是某些方法只能在创建控件底层句柄的线程上运行。并不一定是主线 – Asher 2010-04-08 12:40:51

+0

嗯,你被困在一块岩石和一个坚硬的地方之间。我必须建议你移动摇滚。不知道“原子”应该是什么意思。调用委托目标只能在一个线程上运行*和*执行顺序是有保证的,所以永远不会存在排序或重叠问题。 MessageBox在工作线程上工作正常,但该框可能错误地放在Z顺序中。 NotifyIcon气球是一个更好的鼠标陷阱。 – 2010-04-08 12:41:10

1

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


编辑:队列消费者可以拨打Control.Invoke显示消息框来解决z顺序问题

+0

这与我目前正在实施的解决方案非常相似。 – 2010-04-09 10:20:55

+1

所以给一个兄弟一个投票:) – Asher 2010-04-12 14:02:36

+0

对不起,我太晚了,但给你的评论一个投票。 :-) – 2010-04-12 14:43:56