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 使用的是消息
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章