当使用小窗口工具箱时,了解用于阅读和调度平台 GUI 事件的底层线程模型是很重要的。当在应用程序的代码中使用 Java 线程时,UI 线程的实现将影响应用程序必须遵循的规则。
在任何 GUI 应用程序下面,不管它的语言或用户界面工具箱是什么,OS 平台都会检测 GUI 事件,并将它们放在应用程序事件队列中。尽管在不同的 OS 平台上机制稍微有些不同,但是基础是相似的。当用户单击鼠标、输入字符或者对窗口的外观进行处理时,OS 将生成应用程序 GUI 事件,例如,鼠标单击、击键或者窗口绘制事件。它确定哪个窗口和应用程序应当接收每个事件,并将它放置在应用程序的事件队列中。
任何窗口化的 GUI 应用程序的底层结构都是事件循环。应用程序进行初始化,然后启动循环,它只从队列中阅读 GUI 事件,并相应地作出反应。在处理其中一个事件时完成的任何工作必须快速地进行,以便让 GUI 系统可以响应用户。
应该在单独的线程中执行由用户界面事件触发的长时间操作,以便允许事件循环线程快速返回,并从应用程序的队列中访存下一个事件。然而,必须利用显式锁定和序列化来控制从其它线程访问小窗口和平台 API。未能遵循规则的应用程序可能会导致 OS 调用失败, 更糟糕的是,可能会锁定整个 GUI 系统。
使用 C 语言的本机 GUI 程序员相当熟悉使用平台事件循环的设计注意事项。但是,用 Java 编写的较高级别的小窗口工具箱通常试图通过隐藏平台事件循环来向应用程序开发者隐瞒用户界面线程问题。
实现此过程的常见方法是设置专用工具箱用户界面线程,以便读取和调度事件循环, 并将事件送至由正在单独线程中运行的应用程序服务的内部队列中。 这允许工具箱有足够的时间来响应操作系统, 而不对应用程序处理事件的时间设置任何限制。应用程序必须仍然使用特殊的锁定技术来从它们的应用程序线程中访问用户界面代码, 但是,它在整个代码中的完成是一致的,原因是所有应用程序代码正在非用户界面线程中运行。
尽管听起来好象可以“防止”应用程序发生用户界面线程问题, 但是,实际上它将导致许多问题。
当 GUI 事件的时间取决于 Java 线程实现和应用程序性能时,就很难调试和诊断问题。
目前的 GUI 平台对事件队列执行了许多优化。常见的优化就是将连续的绘制事件折叠到队列中。每当必须重新绘制窗口的一部分时,就可以检查队列是否存在绘制事件重叠或尚未调度的冗余绘制事件。可以将这些事件合并到一个绘制事件中,从而使得闪烁较少出现并且执行应用程序的绘制代码也不会太频繁。如果小窗口工具箱正在将事件快速拉出队列,并将它们发送到内部队列中,则此优化将会失败。
改变开发者对线程模型的理解会导致程序员在使用其它语言和工具箱来为本机 GUI 系统编程时发生混淆。
SWT 遵循平台直接支持的线程模型。应用程序在它的主线程中运行事件循环,并直接从此线程中调度事件。这是应用程序的“UI 线程”。
注意:从技术上来说,UI 线程是创建显示的线程。实际上,此线程也是运行事件循环和创建小窗口的线程。
由于所有事件代码都是从应用程序的用户界面线程中触发的, 因此,处理事件的应用程序代码可以自由地访问小窗口, 并且不需要任何特殊技术就可以进行图形调用。然而,当响应某事件而执行长时间运行的操作时, 应用程序负责创建计算线程的分支。
注意,对于所有来自非用户界面线程的调用,而这些调用又必须来自用户界面线程,SWT 将触发 SWTException。
SWT 应用程序的主线程(包括事件循环)为如下所示:
public static void main (String [] args) { Display display = new Display (); Shell shell = new Shell (display); shell.open (); // start the event loop. We stop when the user has done // something to dispose our window. while (!shell.isDisposed ()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose (); }
一旦创建了小窗口并且打开了 shell,应用程序就会读取和调度 OS 队列中的事件,直到除掉 shell 窗口为止。如果队列中没有事件可用,则说明显示进入睡眠状态,以使其它应用程序有机会运行。
注意:SWT 应用程序的最常见线程模型是在计算线程中运行单个用户界面线程并执行长时间操作。然而,SWT 不会限制开发者使用此模型。应用程序可以运行多个用户界面线程,而每个线程中都有单独的事件循环。
SWT 提供了特殊的访问方法来从后台线程中调用小窗口和图形代码。
希望从非用户界面线程中调用用户界面的应用程序必须提供调用用户界面代码的可运行程序。显示类中的 syncExec(Runnable) 和 asyncExec(Runnable) 方法用来在适当的时间在用户界面线程中执行这些可运行程序。
以下代码片段演示了使用这些方法的模式:
// do time-intensive computations ... // now update the UI. We don't depend on the result, // so use async. Display.getDefault ().asyncExec (new Runnable () { public void run() { myWindow.redraw(); } }); // now do more computations ...
当您从头实现 SWT 应用程序时,线程规则是很清楚的,原因是由您控制事件循环的创建以及将应用程序中的计算线程分支的决定。
如果您正在将插件代码添加至工作台,则没有线程“幻数”隐藏在 JFace 或工作台代码中。规则是简单明了的: