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" |
03 |
xmlns = "org.apache.pivot.wtk" > |
07 |
< TablePane.Column width = "1*" /> |
10 |
< TablePane.Row height = "1*" > |
11 |
< Border styles = "{padding:2}" > |
13 |
< BoxPane styles = "{horizontalAlignment:'center', verticalAlignment:'center'}" > |
14 |
< Label text = "Progress:" /> |
15 |
< Meter wtkx:id = "meter" preferredWidth = "200" preferredHeight = "16" /> |
20 |
< TablePane.Row height = "-1" > |
21 |
< BoxPane styles = "{horizontalAlignment:'center', padding:6}" > |
22 |
< PushButton wtkx:id = "progressButton" styles = "{minimumAspectRatio:3}" /> |
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 ; |
021 |
public Void execute() throws TaskExecutionException { |
024 |
while (percentage < 100 |
030 |
ApplicationContext.queueCallback( new Runnable() { |
033 |
meter.setPercentage(( double )percentage / 100 ); |
036 |
} catch (InterruptedException exception) { |
037 |
throw new TaskExecutionException(exception); |
043 |
private Window window = null ; |
044 |
private Meter meter = null ; |
045 |
private PushButton progressButton = null ; |
046 |
private SampleTask sampleTask = null ; |
048 |
public void startup(Display display, Map<String, String> properties) |
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() { |
056 |
public void buttonPressed(Button button) { |
057 |
if (sampleTask == null ) { |
061 |
sampleTask = new SampleTask(); |
062 |
sampleTask.execute( new TaskAdapter<Void>( new TaskListener<Void>() { |
063 |
@Override taskExecuted在task任務執行結束並且沒有拋出異常時被調用 |
064 |
public void taskExecuted(Task<Void> task) { |
067 |
@Override executeFailed在task任務執行結束並且拋出異常時被調用 |
068 |
public void executeFailed(Task<Void> task) { |
071 |
private void reset() { |
074 |
meter.setPercentage( 0 ); |
075 |
updateProgressButton(); |
082 |
updateProgressButton(); |
085 |
updateProgressButton(); |
086 |
window.open(display); |
089 |
public boolean shutdown( boolean optional) { |
090 |
if (window != null ) { |
096 |
public void suspend() { |
099 |
public void resume() { |
101 |
private void updateProgressButton() { |
102 |
if (sampleTask == null ) { |
103 |
progressButton.setButtonData( "Start" ); |
105 |
progressButton.setButtonData( "Cancel" ); |
108 |
public static void main(String[] args) { |
109 |
DesktopApplicationContext.main(Meters. class , args); |
從上面的代碼我們看到了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 使用的是消息