Eclipse SWT/JFace核心应用——SWT多线程程序设计

SWT中的UI线程

    SWT作为一种桌面程序,比普通的Java程序要多一个UI线程,UI线程负责不断地画出显示的UI控件,当然这个UI线程还要负责事件的处理。什么是事件呢?例如单击按钮或是按下键盘,系统都会生成一个事件放在事件队列中,即接下来UI线程按顺序处理队列中的事件。SWT中Display对象就是一个UI线程,并负责管理队列中的事件。

以下代码,读者并不陌生,在之前使用的SWT程序中都用到过,下面来仔细分析一下它的详细情况。

//当窗口未释放时

while(!shell.isDisposed){

//如果display对象事件队列中没有了等待的事件,就让该线程进入等待状态

if(!display.readAndDispatch)   display.sleep();

}

    可以这样理解UI线程:当程序启动以后,如果用户不进行任何操作,该UI线程就进入了等待状态。一旦触发了某个事件,比如说单击了某个按钮或是按下了键盘上的按键,这时在事件队列中就等待了一个事件,此时UI线程就处理队列中的事件,直到队列中的事件全部处理完毕,又恢复了睡眠状态。处理事件的过程也就是响应用户操作的过程。

    这就产生了一个问题,当某个队列中的事件是一个非常耗时的事件时,比如说是检索文件或者是查询大量数据时,用户就需要长时间等待,会有界面假死的感觉,用户体验很不好,所以在这种情况下,就需要为长时间处理的事件单独开辟出一个线程,放到后台去处理,不影响UI线程急需处理其他事情了,保证界面操作的流畅;后台任务完成后,图形界面获得通知,做出相应的响应。但这种方法在SWT中如何实现呢? 


问题 
SWT在设计上不支持多线程,就是说在非UI线程中调用UI对象是不允许的,若要访问UI界面上的对象必须通过UI线程来访问。SWT在设计上保证在一个线程中使用,这是出于线程安全的考虑。大家可以深入研究一下这么做的目的,我的理解是SWT需要手动释放资源,不想swing那样使用AGC,所以不像swing那样支持多线程。


解决方法 
Display是突破口。Display作为SWT运行时的核心,管理GUI资源,与OS进行通信。 
我们必须为更新GUI的任务开辟一个线程,交由UI线程去调用,而让UI线程调用其他任务的方法是通过Display对象: 


其他线程访问UI线程

理解了线程的基本知识,再重新看一下进度条的示例程序。这个程序是在开始运行窗口时就在后台启动了一个线程,这个后台线程每隔0.1秒更新运行一次设置进度条的值。可以看到在run()方法体重有一段代码读者可能有疑问,重新加注释后的代码如下:

//创建一个线程,该线程每0.1秒更新进度条

Runnable runnable = new Runnable(){

public void run(){

for(int i=minimus; i<maximum; i++){

Thread.sleep(100);

//如果使用以下一行代码更新进度条的值,运行时会出现Invalid thread access运行错误

//progressBar.setSelection(progressBar.getSelection()+1);

//让UI线程更新进度条的值才是正确的

display.asyncExec(new Runnable(){

//这也是一个线程,该线程的功能是更新进度条的值,一瞬间就结束了

//并且这个线程是被UI线程调用的

public void run(){

if(progressBar.isDisposed())   return;

      progressBar.setSelection(progressBar.getSelection()+1);

}

});

}

}

}

//启动这个线程

new Thread(runnable).start();

在设置进度条progressBar的值时,为什么不能直接使用progressBar.setSelection设置值呢?这是因为进度条对象是UI界面上的空间,它是由UI线程创建的。若要访问UI界面上的对象必须通过UI线程来访问,就是说在非UI线程中调用UI对象是不允许的,这是出于线程安全的考虑。正因为如此,只能通过另一种方式来更新进度条的值,解决方案就是开辟新的线程,专门更新滚动条的值,这个线程交由UI线程display来调用。

Display对象调用其他线程有三种方式:

1. asyncExec(Runnable runnable) : 异步启动新的线程。所谓异步就是,UI线程不会等待Runnable对象执行结束后再继续运行;

2. syncExec(Runnable runnable) : 同步启动新的线程。所谓同步就是,UI线程会等待runnable对象执行结束后才会继续进行,当runnable对象是耗时大的线程时,尽量不要采用此种方式。另外,对于这种方式创建的线程可通过getSyncThread()方法获得线程对象。

3. timerExec(int milliseconds, Runnable runnable): 指定一段时间后再启动新线程。用此方法创建的线程,是异步的,如果指定的时间为负数,将不会按时启动线程。


另外Display对象中,与UI线程相关的方法如下所示:

1. 获得当前UI线程对象的方法: getThread(),返回Thread对象;

2. 使UI线程处于休眠状态:sleep();

3. 唤醒UI线程:wake();


获得Display对象 
非UI线程可以通过Display.getDefault()获得。如果之前全局中存在Display对象则直接获得;如果之前没有Display对象存在(如SWT之前没调用Display.getDefault()方法,JFace的ApplicationWindow还没有open()),则该线程编程UI线程。 


调用策略 
暂把为处理耗时任务而开辟的线程叫做任务线程。有两种策略来处理:一是在触发该任务线程时由Display对象启动任务线程(但是这样相当于耗时的任务在UI线程中运行了,界面还是会假死。);二是任务线程中需要更新界面时,通过Display对象启动包含更新动作的新线程。

 

更好的方法 
利用SWT的Action和Contribution。


文章来源:http://ericge.iteye.com/blog/669550  谢谢作者

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值