Apache Pivot background Task 和 UI thread

Apache Pivot后台线程与UI线程
文章中用到的一些术语的说明:
UI操作:修改了UI组件的某些属性或则特性,比如修改按钮显示的文本或则图标等或读取UI组件的属性或者特性。
非UI操作:不能有修改或则读取任何与UI组件相关的属性或者特性。
background Task(后台任务): 用于执行非UI操作的线程。
UI thread:用于执行UI操作的线程,一般一个应用程序只有一个UI线程。
很多UI框架,用户界面都有一个独立的线程,称作 UI thread。用户在UI上的操作有些很快就完成,然而有些操作需要耗时比较长的时间(比如同文件加载数据或者从数据库查询数据或者从网络请求数据等),在这种情况下,如果把操作放在UI thread中执行,整个用户界面就会被"挂起"了,直到操作完成用户才可以进行其它的UI操作,当然这种低效率的操作是开发者所不允许的,很多UI框架都采用了另外一个独立于UI thread的线程来执行耗时比较长的由UI操作引入的非UI操作。
在Pivot中,用于执行耗时较长的操作的,一般使用后台任务(background task)来实现,后台任务在Pivot中由类org.apache.pivot.util.concurrent.Task表示
对于后台任务有一些限制:
1. 由于Pivot的对UI 组件的操作只能在UI thread中,因此所有后台任务是不能操作任何UI组件的。
2.后台任务的执行会有一个输出结果,但是UI thread并不知道后台任务何时执行结束以及执行结果?
 
为了解决上面2个后台任务的限制,Pivot提供了不同的方法来解决这些问题:
1.在后台任务(非UI线程)操作UI组件时,通过ApplicationContext.queueCallback方法,把要执行的内容加入到UI可执行队列中,该队列的可执行体由UI thread负责轮询调用,类似与其他UI框架中的消息机制。
2. UI thread为了知道UI组件操作所创建的后台任务的执行结果,可以使用TaskAdapter和TaskListener监听后台任务的执行结果,并把任务执行后要执行的操作加入UI thread,由UI thread负责执行实际的代码。
 
下面我们开看一下Pivot tutorial的Meter代码,并分析后台线程和UIthread之间的互操作。
后台线程与UI Thread之间的互操作包括: UI thread 创建执行后台线程和后台线程修改UI 组件。
 
1. UI界面的WTKX代码
<Window title="Meters" maximized="true"
02     xmlns:wtkx="http://pivot.apache.org/wtkx"
03     xmlns="org.apache.pivot.wtk">
04     <content>
05         <TablePane>
06             <columns>
07                 <TablePane.Column width="1*"/>
08             </columns>
09             <rows>
10                 <TablePane.Row height="1*">
11                     <Border styles="{padding:2}">
12                         <content>
13                             <BoxPane styles="{horizontalAlignment:'center', verticalAlignment:'center'}">
14                                 <Label text="Progress:"/>
15                                 <Meter wtkx:id="meter" preferredWidth="200" preferredHeight="16"/>
16                             </BoxPane>
17                         </content>
18                     </Border>
19                 </TablePane.Row>
20                 <TablePane.Row height="-1">
21                     <BoxPane styles="{horizontalAlignment:'center', padding:6}">
22                         <PushButton wtkx:id="progressButton" styles="{minimumAspectRatio:3}"/>
23                     </BoxPane>
24                 </TablePane.Row>
25             </rows>
26         </TablePane>
27     </content>
28 </Window>
 
UI界面的Java 代码:
在下面的代码中,我们实现了一个Task的类,叫SampleTask,用于模拟长时间允许的后台任务,并在任务的执行过程中修改UI组件Meter的相关操作。
 
package org.apache.pivot.tutorials.progress;
002 import org.apache.pivot.collections.Map;
003 import org.apache.pivot.util.concurrent.Task;
004 import org.apache.pivot.util.concurrent.TaskExecutionException;
005 import org.apache.pivot.util.concurrent.TaskListener;
006 import org.apache.pivot.wtk.Application;
007 import org.apache.pivot.wtk.ApplicationContext;
008 import org.apache.pivot.wtk.Button;
009 import org.apache.pivot.wtk.ButtonPressListener;
010 import org.apache.pivot.wtk.DesktopApplicationContext;
011 import org.apache.pivot.wtk.Display;
012 import org.apache.pivot.wtk.Meter;
013 import org.apache.pivot.wtk.PushButton;
014 import org.apache.pivot.wtk.TaskAdapter;
015 import org.apache.pivot.wtk.Window;
016 import org.apache.pivot.wtkx.WTKXSerializer;
017 public class Meters implements Application {
018     public class SampleTask extends Task<Void> {
019         private int percentage = 0;
020         @Override
021         public Void execute() throws TaskExecutionException {
022             // 模拟一个长时间的操作任务
023             percentage = 0;
024             while (percentage < 100
025                 && !abort) {
026                 try {
027                     Thread.sleep(100);
028                     percentage++;
029                     // 在UI thread中更新meter的进度,在这里不能直接调用组件的代码,因为该线程是非UI线程,不能操作任何UI组件。通过queueCallback把要执行的代码加入到UI thread的执行队列中,由UI thread负责执行。
030                     ApplicationContext.queueCallback(new Runnable() {
031                         @Override
032                         public void run() {
033                             meter.setPercentage((double)percentage / 100);
034                         }
035                     });
036                 } catch(InterruptedException exception) {
037                     throw new TaskExecutionException(exception);
038                 }
039             }
040             return null;
041         }
042     }
043     private Window window = null;
044     private Meter meter = null;
045     private PushButton progressButton = null;
046     private SampleTask sampleTask = null;
047     @Override
048     public void startup(Display display, Map<String, String> properties)
049         throws Exception {
050         WTKXSerializer wtkxSerializer = new WTKXSerializer();
051         window = (Window)wtkxSerializer.readObject(this, "meters.wtkx");
052         meter = (Meter)wtkxSerializer.get("meter");
053         progressButton = (PushButton)wtkxSerializer.get("progressButton");
054         progressButton.getButtonPressListeners().add(new ButtonPressListener() {
055             @Override
056             public void buttonPressed(Button button) {
057                 if (sampleTask == null) {
058                     // 创建并驱动一个模型的后台任务; 封装在一个task adapter对象中, 
059                     // 使得UI thread可以调用后台任务执行完成后的回调代码。 
060                     // 如果不能用Adpater时,执行完成后的回调代码不能调用任何UI操作。 
061                     sampleTask = new SampleTask();
062                     sampleTask.execute(new TaskAdapter<Void>(new TaskListener<Void>() {
063                         @Override taskExecuted在task任务执行结束并且没有抛出异常时被调用 
064                         public void taskExecuted(Task<Void> task) {
065                             reset();
066                         }
067                         @Override executeFailed在task任务执行结束并且抛出异常时被调用 
068                         public void executeFailed(Task<Void> task) {
069                             reset();
070                         }
071                         private void reset() {
072                             // Reset the meter and button
073                             sampleTask = null;
074                             meter.setPercentage(0);
075                             updateProgressButton();
076                         }
077                     }));
078                 } else {
079                     // Cancel the task
080                     sampleTask.abort();
081                 }
082                 updateProgressButton();
083             }
084         });
085         updateProgressButton();
086         window.open(display);
087     }
088     @Override
089     public boolean shutdown(boolean optional) {
090         if (window != null) {
091             window.close();
092         }
093         return false;
094     }
095     @Override
096     public void suspend() {
097     }
098     @Override
099     public void resume() {
100     }
101     private void updateProgressButton() {
102         if (sampleTask == null) {
103             progressButton.setButtonData("Start");
104         } else {
105             progressButton.setButtonData("Cancel");
106         }
107     }
108     public static void main(String[] args) {
109         DesktopApplicationContext.main(Meters.class, args);
110     }
111 }
 
从上面的代码我们看到了UI 线程如何启动一个后台线程,并且要求后台线程执行结束后通知UI线程 和后台线程如何执行UI操作的方法。注意:这里所的后台线程执行UI的操作,并不是指UI操作的代码在后台线程中执行,而是后台线程把要执行的UI操作的代码加入UI thread的执行队列,由UI thread轮询执行(因为UI thread本质上就是一个轮询UI事件的线程)。
 
task对象在调用execute方法时,如果不传递任何参数,那么等同于在UI thread直接执行操作一样,没有创建任何后台线程。
如果task对象在调用execute时,传递的参数为TaskListener的一个实现的对象(非TaskAdapter)时,在taskExecuted和executeFailed回调方法中不能直接调用UI组件的任何操作代码,除非传递的参数对象为TaskAdapter才可以直接调用,或者在传递TaskListener的实现(非TaskAdapter)的实例时,如果一定要操作UI组件,那么可以使用ApplicationContext.queueCallback对要执行的相关UI操作进行封装前转UI操作到UI thread执行。
 
示例中使用的就是传递一个TaskAdapter的一个实例作为execute的参数。因此可以在TaskListener的实现中直接调用UI操作,因为TaskAdapter的taskExecuted和executeFailed已经为我们封装了相关的代码,使得操作可以被前转到UI thread执行。
 
如果熟悉了解SWT的,可能会更容易理解上面的代码。其实很多UI都是类似的做法只是表示方式不同而已,比如古老的MFC 使用的是消息
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章