Apache Pivot background Task 和 UI thread

来源:互联网 发布:淘宝全球战略 编辑:程序博客网 时间:2024/05/17 02:07
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;
002import org.apache.pivot.collections.Map;
003import org.apache.pivot.util.concurrent.Task;
004import org.apache.pivot.util.concurrent.TaskExecutionException;
005import org.apache.pivot.util.concurrent.TaskListener;
006import org.apache.pivot.wtk.Application;
007import org.apache.pivot.wtk.ApplicationContext;
008import org.apache.pivot.wtk.Button;
009import org.apache.pivot.wtk.ButtonPressListener;
010import org.apache.pivot.wtk.DesktopApplicationContext;
011import org.apache.pivot.wtk.Display;
012import org.apache.pivot.wtk.Meter;
013import org.apache.pivot.wtk.PushButton;
014import org.apache.pivot.wtk.TaskAdapter;
015import org.apache.pivot.wtk.Window;
016import org.apache.pivot.wtkx.WTKXSerializer;
017public 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 使用的是消息
 
原创粉丝点击