当违反Swing线程策略时会发生什么?

6
在过去的几年中,我主要在Eclipse中进行UI开发,它在线程访问方面非常保守:任何试图从UI线程外部更改UI小部件(例如颜色、文本)属性的尝试都会引发异常。
现在我正在查看一个使用Swing的现有程序,其中一个窗口具有大量自定义小部件。有一个单独的线程运行每个这些小部件的变异函数,变异函数读取某些内容的值(例如标签颜色和值),并写入一些内容(例如更改背景颜色)。请注意,没有涉及自定义绘画或类似的内容,只是对其包含的子小部件(大多数为JLabels)进行了一堆更改。
目前,这是从单独的线程运行的,而不是从Swing事件线程运行的。该线程遍历所有400个小部件,并在每个小部件上调用变异器。更新似乎正常工作,但GUI对用户输入无响应。
如果我将整个约0.4毫秒运行时间从Swing线程外部获取,并在每次调用变异器时包装在invokeLater或invokeAndWait中,则UI的响应性更高。
我想要理解的是:
1)有时从Swing线程外部进行所有这些调用是否合法?
2)对Swing线程的影响是什么,为什么从外部调用时UI不够响应?
5个回答

3

从“无”到间歇性问题再到“所有东西都坏了,让每个人都去修GUI!”

最明显的主要视觉效果是,如果你阻塞了GUI线程(比如有人按下按钮并且你执行了sleep(5000)之类的操作),你的GUI将不会重新绘制。这是因为你正在占用它唯一可以传递给你的线程!这使得人们认为Java非常慢。它并不差,但编程很容易,因此很多人没有研究这样的实践方法就生产出了产品。

接下来最大的问题是,在另一个线程中绘制屏幕时(比如传递给main的线程),可能会出现奇怪的行为。Swing已经对你如何渲染框架过于挑剔了——把线程作为变量排除在外!

最后,很少情况下(或者如果你在错误的线程上调用Swing组件,则经常发生)可能会发生线程冲突。如果发生这种情况,可能会抛出异常(或者不会),并且某些内容可能会呈现错误,但这可能并不明显。


3

1)有时候从Swing线程外部进行所有这些调用是合法的吗?

有一些例外情况(例如设置文本字段的文本值会自动进行EDT代理),但没有任何情况是最好这样做的。如果您要执行大量更新,可以在单个EDT调用中执行它们(单个调用invokeLater()),而不是单独调用 - 但即使那种批处理也很少有帮助。长话短说:从EDT上执行Swing组件的操作。这包括读取和写入。

2)从外部调用的影响对Swing线程有什么影响,为什么UI响应速度较慢?

好吧,EDT负责更新GUI。如果您从外部调用,它并不“响应速度较慢” - 实际上低级系统调用根本没有更新用户界面。在您的应用程序中可能发生的情况是,原始开发人员在不创建真正恶劣的竞争条件的情况下幸运地更改了Swing组件的状态。然后,某些其他事件导致必须在EDT上重新绘制,从而导致组件被更新。这可能看起来像“缺乏响应能力”,但实际上是“缺乏屏幕刷新”。

EDT只是一个常规线程,但它有点特殊,因为它在紧密循环中运行,处理与GUI相关的信号(例如绘制命令)。将这些类型的命令发布到EDT的语义与我们通常认为的Java线程非常不同(它涉及向消息泵提交操作)。

长话短说 - 所有那些说“只与EDT上的Swing对象交互”的Javadocs都是有原因的。不要乱搞。如果您想进行后台处理,可以 - 但您需要负责将与J*组件的交互代理回EDT(通常使用invokeLater())。


3
  1. 确实没有例外。Kevin部分正确 - JTextComponent.setText()被宣传为线程安全。然而,在查看1.6代码时,它对文档对象提供了同步,并且没有使用EDT。这很好,除非另一个Swing组件(或控制Swing组件的东西)正在监听文档对象。为了避免麻烦,请始终使用EDT - 像Kevin说的那样,我不知道还有其他情况。

  2. 如果没有深入挖掘代码,很难说;行为是未定义的。如果您的后台任务运行时间长(>几秒钟),则会看到相反的效果 - 使用EDT将使UI在任务运行时无响应。

幸运的是,按照正确的方式做事情对您来说似乎是最好的。:)

Sun曾经说可以在尚未实现的组件中使用其他线程,但后来收回了:

相关的stackoverflow问题

请查看Sun关于Swing和并发性的UI教程(我想发布链接,但这是我在stackoverflow上的第一个答案)。


1
基本问题在于非线程安全对象以多线程方式执行。即使像读取 HashMap 这样简单的操作也可能陷入无限循环。因为 AWT 使用锁(不好地),你也可能遇到死锁。然而,你可能会逃脱这种情况,尽管你可能会发现 Java 的更新版本突然在某些客户机器上引起问题。
(顺便说一下:这是 AWT 的事件分派线程,而不是 Swing 的。)

1

通常情况下,任何更新屏幕像素的内容都必须从事件分发线程(EDT)中调用。虽然某些JVM可能会在EDT之外处理更新,但您永远不应该依赖于此工作,因为某些计算机和不同的外观和感觉将无法正常工作。行为是未定义的 - 这可能解释了您所看到的缺乏响应性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接