SWT 多线程

来源:互联网 发布:微信商城淘宝客 编辑:程序博客网 时间:2024/05/17 13:07

 

 

最近工作中要做一件这样的事情,就是在一个已有的SWT界面上对一些业务层写好的事件做响应,也就是是SWT的组件订阅了某个时间Listener,当事件触发的时候响应的SWT组件会做出一些信息的显示工作。一开始我们的程序是这样写的:我们所期待的那样做出了反应。问题解决了。总结一下也就是 SWT的在对其组件进行操作的时候有一个单线程的约束,直接的赋值方式是行不通的,SWT在*.widget.Display类中提供了两个方法可以间接的在非用户线程的进行图形构件的访问操作,这是通过的syncExec(Runnable)和asyncExec(Runnable)这两个方法去实现的。而方法syncExec()和asyncExec()的区别在于前者要在指定的线程执行结束后才返回,而后者则无论指定的线程是否执行都会立即返回到当前线程。
我们会在一个业务的监听器中将我们需要进行设置(重绘)的SWT组件从其构造函数中传过去:

java 代码
 
  1. ServiceListener serviceListener = new ServiceListener(txtInformation);  


这里做下说明,ServiceListener是我们自己实现的一个实现了javax.management.NotificationListener 接口的Listener ,它必须实现其handleNotification方法,而txtInformation是一个swt.Text的组件。我们想当然的在这个方法中做一些跟SWT有关的操作于是我们将这个Text组件传进去,再在响应事件中对其进行操作。

java 代码
 
  1. public class ServiceListener implements NotificationListener {  
  2.   
  3. public ServiceListener(Text information) {  
  4. // TODO Auto-generated constructor stub  
  5. super();  
  6. txtInformation = information;  
  7. }  
  8.   
  9. Text txtInformation;  
  10.   
  11. public void handleNotification(Notification notification, Object handback) {  
  12.   
  13. ... ....  
  14.   
  15. //比如说我们取到某些值想要把它赋值给我们的SWT组件  
  16.   
  17. String result = "this is a test!";  
  18.   
  19. txtInformation = result;  
  20.   
  21. }  
  22.   
  23. }  


我们期待的是,当我们期待的事件发生后,会触发这个响应事件做出响应,而我们在已有的SWT界面上的TEXT组件的值会发生改变。但事实上事件是触发了,但程序走到 “txtInformation.setText(result); ”这一行的时候无端停住了,并报了个异常。单我们但不跟踪并查看SWT的源代码的时候看到Text组件中的setText方法是这样写的:
java 代码
 
  1. public void setText (String string) { 

  2. checkWidget (); 

  3. if (string == null) error (SWT.ERROR_NULL_ARGUMENT); 

  4. string = Display.withCrLf (string); 

  5. if (hooks (SWT.Verify) || filters (SWT.Verify)) { 

  6. int length = OS.GetWindowTextLength (handle); 

  7. string = verifyText (string, 0, length, null); 

  8. if (string == nullreturn

  9. }  
  10. TCHAR buffer = new TCHAR (getCodePage (), string, true); 

  11. OS.SetWindowText (handle, buffer);  
  12. /* 
  13. * Bug in Windows.  When the widget is multi line 
  14. * text widget, it does not send a WM_COMMAND with 
  15. * control code EN_CHANGE from SetWindowText () to 
  16. * notify the application that the text has changed. 
  17. * The fix is to send the event. 
  18. */  
  19. if ((style & SWT.MULTI) != 0) { 

  20. sendEvent (SWT.Modify);  
  21. // widget could be disposed at this point 



  22. }  


其中checkWidget ();方法引起了我们的注意,进去看看:
java 代码
 
  1. /** 
  2. * Throws an SWTException if the receiver can not 
  3. * be accessed by the caller. This may include both checks on 
  4. * the state of the receiver and more generally on the entire 
  5. * execution context. This method should be called by 
  6. * widget implementors to enforce the standard SWT invariants. 
  7. * 
  8.  
  9. * Currently, it is an error to invoke any method (other than 
  10. isDisposed()) on a widget that has had its 
  11. dispose() method called. It is also an error 
  12. * to call widget methods from any thread that is different 
  13. * from the thread that created the widget. 
  14. * 
  15.  
  16. * In future releases of SWT, there may be more or fewer error 
  17. * checks and exceptions may be thrown for different reasons. 
  18. * 
  19.  
  20. * 
  21. * @exception SWTException 
    •   * 
    •     
    • ERROR_WIDGET_DISPOSED - if the receiver has been disposed  
    • * 
    •     
    • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver  
    • *
     
  22. */  
  23. protected void checkWidget () { 

  24. Display display = this.display; 

  25. if (display == null) error (SWT.ERROR_WIDGET_DISPOSED); 

  26. if (display.thread != Thread.currentThread ()) error (SWT.ERROR_THREAD_INVALID_ACCESS);
  27.   
  28. if ((state & DISPOSED) != 0) error (SWT.ERROR_WIDGET_DISPOSED); 

  29. }  

看见没有,事实上这里明显的是不能在多个线程中对同一个SWT组件进行操作,从ibm developerworks上了解到:Java语言本身就提供了多线程机制,这种机制对GUI编程来说是不利的,它不能保证图形构件操作的同步与串行化。SWT采用了一种简单而直接的方式去适应本地GUI系统对线程的要求:在SWT中,通常存在一个被称为"用户线程"的唯一线程,只有在这个线程中才能调用对构件或某些图形API的访问操作。如果在非用户线程中程序直接调用这些访问操作,那么SWTExcepiton异常会被抛出。

那么怎么解决这个问题了,按照developerworks提出的方案,我们对我们的之前写的程序稍微做了一些改进:


java 代码
 
  1. public class ServiceListener implements NotificationListener {  
  2.   
  3. public ServiceListener(Text information) {  
  4. // TODO Auto-generated constructor stub  
  5. super();  
  6. txtInformation = information;  
  7. }  
  8.   
  9. Text txtInformation;  
  10.   
  11. public void handleNotification(Notification notification, Object handback) {  
  12.   
  13. ... ....  
  14.   
  15. //比如说我们取到某些值想要把它赋值给我们的SWT组件  
  16.   
  17. String result = "this is a test!";  
  18.   
  19. //以下是新加的:  
  20.   
  21. txtInformation.getDisplay().asyncExec(new Runnable() {  
  22.   
  23. public void run() {  
  24. txtInformation.setText(result);  
  25. }  
  26. });  
  27.   
  28. }  
  29.   
  30. }  


很好程序如我们所期待的那样做出了反应。问题解决了。总结一下也就是 SWT的在对其组件进行操作的时候有一个单线程的约束,直接的赋值方式是行不通的,SWT在*.widget.Display类中提供了两个方法可以间接 的在非用户线程的进行图形构件的访问操作,这是通过的syncExec(Runnable)和asyncExec(Runnable)这两个方法去实现 的。而方法syncExec()和asyncExec()的区别在于前者要在指定的线程执行结束后才返回,而后者则无论指定的线程是否执行都会立即返回到 当前线程。

 

Display.getCurrent().AsyncExec(new Runnable(){
public void run(){
//在这里随便写点什么都可以了。比如 text1.setText(...);
}
});

其实看看SWT源码就都清楚了,这里建立的 Runnable 接口 的实例根本不会成为一个独立的线程,只是由Synchronizer调用其中的 run 方法而已。
虽然在这里多数是用来传递系统消息,不过你顺便做点别的,也不反对。
还是搬一点正宗的理论来吧,免得误人。
线程问题
当使用小窗口工具箱时,了解用于阅读和调度平台 GUI 事件的底层线程模型是很重要的。当在应用程序的代码中使用 Java 线程时,UI 线程的实现将影响应用程序必须遵循的规则。

本机事件调度
在任何 GUI 应用程序下面,不管它的语言或用户界面工具箱是什么,OS 平台都会检测 GUI 事件,并将它们放在应用程序事件队列中。尽管在不同的 OS 平台上机制稍微有些不同,但是基础是相似的。当用户单击鼠标、输入字符或者对窗口的外观进行处理时,OS 将生成应用程序 GUI 事件,例如,鼠标单击、击键或者窗口绘制事件。它确定哪个窗口和应用程序应当接收每个事件,并将它放置在应用程序的事件队列中。

任何窗口化的 GUI 应用程序的底层结构都是事件循环。应用程序进行初始化,然后启动循环,它只从队列中阅读 GUI 事件,并相应地作出反应。在处理其中一个事件时完成的任何工作必须快速地进行,以便让 GUI 系统可以响应用户。

应该在单独的线程中执行由用户界面事件触发的长时间操作,以便允许事件循环线程快速返回,并从应用程序的队列中访存下一个事件。然而,必须利用显式锁定和序列化来控制从其它线程访问小窗口和平台 API。未能遵循规则的应用程序可能会导致 OS 调用失败,更糟糕的是,可能会锁定整个 GUI 系统。

工具箱用户界面线程
使用 C 语言的本机 GUI 程序员相当熟悉使用平台事件循环的设计注意事项。但是,用 Java 编写的较高级别的小窗口工具箱通常试图通过隐藏平台事件循环来向应用程序开发者隐瞒用户界面线程问题。

实现此过程的常见方法是设置专用工具箱用户界面线程,以便读取和调度事件循环,并将事件送至由正在单独线程中运行的应用程序服务的内部队列中。这允许工具箱有足够的时间来响应操作系统,而不对应用程序处理事件的时间设置任何限制。应用程序必须仍然使用特殊的锁定技术来从它们的应用程序线程中访问用户界面代码,但是,它在整个代码中的完成是一致的,原因是所有应用程序代码正在非用户界面线程中运行。

尽管听起来好象可以“防止”应用程序发生用户界面线程问题,但是,实际上它将导致许多问题。

当 GUI 事件的时间取决于 Java 线程实现和应用程序性能时,就很难调试和诊断问题。

目前的 GUI 平台对事件队列执行了许多优化。常见的优化就是将连续的绘制事件折叠到队列中。每当必须重新绘制窗口的一部分时,就可以检查队列是否存在绘制事件重叠或尚未调度的冗余绘制事件。可以将这些事件合并到一个绘制事件中,从而使得闪烁较少出现并且执行应用程序的绘制代码也不会太频繁。如果小窗口工具箱正在将事件快速拉出队列,并将它们发送