转自:http://book.csdn.net/bookfiles/304/10030412715.shtml
参考:(1)http://www.ibm.com/developerworks/cn/opensource/os-cn-eclipse-multithrd/index.html (2)http://liusu.blog.ccidnet.com/blog-htm-do-showone-uid-2571-itemid-104334-type-blog.html 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线程继续处理其他事件了,这样 给用户的感觉就是,虽然后台运行着程序,但也不会影响界面上的操作。 11.3 其他线程访问UI线程理解了线程的基本知识,再重新看一下9.7节中进度条的示例程序。这个程序是在开始运行窗口时就在后台启动了一个线程,这个后台线程每隔0.1秒更新运行一次设置滚动条的值。可以看到在run()方法体中有一段代码读者可能有疑问,重新加注释后的代码如下: //创建一个线程,该线程每0.1秒更新一次滚动条的值 Runnable runnable = new Runnable() { public void run() { //线程运行的主体 for (int i = minimus; i < maximum; i++) { try { //让线程睡眠0.1秒 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //如果使用以下一行代码更新滚动条的值,运行时会出现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 (progressBar.getSelection() + 1)代码来直接设置滚动条的值呢?这是因为滚动条对象是UI界面上的控件,它是由UI线程创建的。若要访问UI界面上的对象必须通过UI线程来访问,就是 说在非UI线程中调用UI对象是不允许的,这是出于线程安全的考虑。正因为如此,只能通过另一种方式来更新进度条的值,解决方案就是需要再开辟一个线程, 专门更新滚动条的值,这个线程交给UI线程来调用。 Display对象中负责调用其他线程的方法有以下3种: ● asyncExec(Runnable runnable):异步启动新的线程。所谓异步就是,UI线程不会等待runnable对象执行结束后再继续进行,就是说UI线程可以和runnable对象所在的线程同时运行。 ● syncExec(Runnable runnable):同步启动新的线程。所谓同步就是,UI线程会等待runnable对象执行结束后才会继续进行,当runnable对象是耗时大的线 程时,尽量不要采用此种方式。另外,对于该种方式创建的线程可通过getSyncThread()方法获得线程对象。 ● timerExec(int milliseconds,Runnable runnable):指定一段时间再启动新的线程。用此方法创建的线程,将会在指定的时间后再启动线程。当然用此方法创建的线程启动后,与UI线程是异步 的。如果指定的时间为负数,将不会按时启动线程。 另外Display对象中,与UI线程相关的方法如下所示: ● 获得当前的UI线程对象的方法:getThread(),返回Thread对象。 ● 使UI线程处于休眠状态:sleep()。 ● 唤醒UI线程:wake()。 (1)编写一个MutiTaskTestDrive类,作为该系统的入口。该类比较简单,代码如下: MutiTaskTestDrive.java package com.fengmanfei.ch11; import org.eclipse.swt.widgets.Display; import com.fengmanfei.util.ImageFactory; public class MutiTaskTestDrive { public static void main(String[] args) { Display display = Display.getDefault(); MutiTaskGUI mutiTask= new MutiTaskGUI(); mutiTask.getShell().open(); while (!mutiTask.getShell().isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } ImageFactory.dispose(); display.dispose(); } } (2)MutiTaskGUI类为主窗口类,也就是放置表格的窗口,该类具体的代码如下: MutiTaskGUI.java package com.fengmanfei.ch11; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; public class MutiTaskGUI { private Shell shell = null; private Table table = null; public MutiTaskGUI( ){ //构造方法中调用初始化窗口的方法 init(); } //初始化窗口方法 public void init() { shell = new Shell(); shell.setLayout(new GridLayout()); shell.setText("多线程"); Button bt = new Button ( shell , SWT.NONE); bt.setText("开始一个任务"); // 创建表格对象 table = new Table(shell, SWT.BORDER); table.setLayoutData( new GridData(SWT.FILL,SWT.FILL,true,true)); table.setHeaderVisible(true); table.setLinesVisible(true); String[] header = new String[]{"任务","进度","操作"}; // 创建表头 for (int i = 0; i < 3; i++) { TableColumn col = new TableColumn(table, SWT.NONE); col.setText( header[i] ); } //设置表头宽度 table.getColumn(0).setWidth(80); table.getColumn(1).setWidth(150); table.getColumn(2).setWidth(80); shell.pack(); //注册创建任务按钮事件 bt.addSelectionListener( new SelectionAdapter(){ //当单击创建一个任务按钮时 public void widgetSelected(SelectionEvent e) { //首先创建一个Task对象 Task task = new Task ( table ); //然后在表格中添加一行 task.createTableItem(); //最后启动该任务,该任务为一个线程 task.start(); } }); } //获得和设置属性的getter和setter方法 public Shell getShell() { return shell; } public void setShell(Shell shell) { this.shell = shell; } public Table getTable() { return table; } public void setTable(Table table) { this.table = table; } } 该类需要注意的地方是,“开始一个任务”按钮事件的处理。当单击该按钮时,就创建一表格中的一行,并且启动一个线程。添加表格中的一行和启动线程是使用Task对象来完成的。 (3)Task类继承自Thread,并覆盖了run()方法,具有线程的特性。Task类具体实现的代码如下: Task.java package com.fengmanfei.ch11; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.TableEditor; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.ProgressBar; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableItem; import com.fengmanfei.util.ImageFactory; //该Task类继承自Thread,并且覆盖了run()方法 public class Task extends Thread { //该类的一些属性 private Table table = null; //是否停止的标志 private boolean done = false; //声明进度条对象 private ProgressBar bar = null; private int min = 0; private int max = 100; public Task(Table table) { this.table = table; } //创建表格中的一行 public void createTableItem() { TableItem item = new TableItem(table, SWT.NONE); item.setText(this.getName()); item.setImage(ImageFactory.loadImage(table.getDisplay(), ImageFactory.PROGRESS_TASK)); // 创建一个进度条 bar = new ProgressBar(table, SWT.NONE); bar.setMinimum(min); bar.setMaximum(max); // 创建一个可编辑的表格对象 TableEditor editor = new TableEditor(table); editor.grabHorizontal = true; editor.grabVertical = true; // 将进度条绑定到第二列中 editor.setEditor(bar, item, 1); //重新创建一个可编辑的表格对象 editor = new TableEditor(table); editor.grabHorizontal = true; editor.grabVertical = true; //创建一个按钮 Button stop = new Button(table, SWT.NONE); stop.setText("Stop"); editor.setEditor(stop, item, 2); stop.addSelectionListener(new SelectionAdapter() { //当停止按钮按下时,设置停止标记true public void widgetSelected(SelectionEvent e) { if (!isDone()) setDone(true); } }); } //线程方法体,与前面单个的进度条的程序类似 public void run() { for (int i = min; i < max; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } table.getDisplay().asyncExec(new Runnable() { public void run() { if (bar.isDisposed()) return; bar.setSelection(bar.getSelection() + 1); } }); //如果停止,则结束该线程 if (isDone()) { break; } } } //获得和设置属性的getter和setter方法 public Table getTable() { return table; } public void setTable(Table table) { this.table = table; } public boolean isDone() { return done; } public void setDone(boolean done) { this.done = done; } } Task类中的run()方法体中的代码与之前单个进度条的处理方式类似。从以上的程序代码中可以看出,类中大量使用了bean的方式,也就是通过一些getter和setter方法来设置和访问类的属性,又将常用的操作封装为方法,这才是涉及结构合理的类。 小结:对SWT程序中特有的线程——UI线程由Display对象负责管理,是SWT程序运行的核心。最值得注意的是,如果在 非UI线程中调用界面上的控件对象是会在运行期间报错,所以Display对象使用asyncExec(Runnable runnable)和syncExec(Runnable runnable)方法更新界面的控件对象。 |