如果所有任務(wù)的執(zhí)行時間都較短(并且應(yīng)用程序中不包含執(zhí)行時間較長的非GUI部分),那么整個應(yīng)用程序都可以在事件線程內(nèi)部運行,并且完全不用關(guān)心線程。然而,在復(fù)雜的GUI?應(yīng)用程序中可能包含一些執(zhí)行時間較長的任務(wù),并且可能超過了用戶可以等待的時間,例如拼寫檢查、后臺編輯或者獲取遠(yuǎn)程資源等。這些任務(wù)必須在另一個線程中運行,才能使得GUI在運行時保持高響應(yīng)性。
Swing 使得在事件線程中運行任務(wù)更容易,但(在Java 6之前)并沒有提供任何機(jī)制來幫助GUI任務(wù)執(zhí)行其他線程中的代碼。然而在這里不需要借助于Swing:可以創(chuàng)建自己的Executor 來執(zhí)行長時間的任務(wù)。對于長時間的任務(wù),可以使用緩存線程池。只有GUI應(yīng)用程序很少會發(fā)起大量的長時間任務(wù),因此即使線程池可以無限制地增長也不會有太大的風(fēng)險。
首先來看一個簡單的任務(wù),該任務(wù)不支持取消操作和進(jìn)度指示,也不會在完成后更新GUI,我們之后再將這些功能依次添加進(jìn)來。在程序清單9-4中給出了一個與某個可視化組件綁定的監(jiān)聽器,它將一個長時間的任務(wù)提交給一個Executor。盡管有兩個層次的內(nèi)部類,但通過這種方式使某個GUI任務(wù)啟動另一個任務(wù)還是很簡單的:在事件線程中調(diào)用UI動作監(jiān)聽器,然后將一個Runnable提交到線程池中執(zhí)行。
這個示例通過“Fire and Forget”與方式將長時間任務(wù)從事件線程中分離出來,這種方式可能并不是非常有用。在執(zhí)行完一個長時間的任務(wù)后,通常會產(chǎn)生某種可視化的反饋。但你并不能從后臺線程中訪問這些表現(xiàn)對象,因此任務(wù)在完成時必須向事件線程提交另一個任務(wù)來更新用戶界面。
????????????????????????????????????程序清單9-4 ?????將一個長時間任務(wù)綁定到一個可視化組件 ????????
ExecutorService backgroundExec =Executors. newCachedThreadPool();
…
button,addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
backgroundExec. execute(new Runnable(){
public void run(){doBigComputation();}
} ) ?;
} } ) ;????????????????????????????????????????????????
程序清單9-5給出了如何實現(xiàn)這個功能的方式,但此時已經(jīng)開始變得復(fù)雜了,即已經(jīng)有了三層的內(nèi)部類。動作監(jiān)聽器首先使按鈕無效,并設(shè)置一個標(biāo)簽表示正在進(jìn)行某個計算,然后將一個任務(wù)提交給后臺的Executor。當(dāng)任務(wù)完成時,它會在事件線程中增加另一個任務(wù),該任務(wù)將重新激活按鈕并恢復(fù)標(biāo)簽文本。
???????????????????????????程序清單9-5支持用戶反饋的長時間任務(wù) ??????????????????????????????????????
button,addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
button. setEnabled(false);
label. setText("busy");
backgroundExec. execute(new Runnable(){
public void run(){
try {
doBigComputation();
}finally {
GuiExecutor. instance(). execute(new Runnable(){
public void run(){
button. setEnabled(true);
label. setText("idle");
}
} ) ?;
}
}
} ) ?;
﹞ |
} ?) ?;
??????????????????????????????????????????????????????????????????????
在按下按鈕時觸發(fā)的任務(wù)中包含3個連續(xù)的子任務(wù),它們將在事件線程與后臺線程之間交替運行。第一個子任務(wù)更新用戶界面,表示一個長時間的操作已經(jīng)開始,然后在后臺線程中啟動第二個子任務(wù)。當(dāng)?shù)诙€子任務(wù)完成時,它把第三個子任務(wù)再次提交到事件線程中運行,第三個子任務(wù)也會更新用戶界面來表示操作已經(jīng)完成。在GUI應(yīng)用程序中,這種“線程接力”是處理長時間任務(wù)的典型方法。
? 取消
當(dāng)某個任務(wù)在線程中運行了過長時間還沒有結(jié)束時,用戶可能希望取消它。你可以直接通過線程中斷來實現(xiàn)取消操作,但是一種更簡單的辦法是使用Future,專門用來管理可取消的任務(wù)。
如果調(diào)用Future的cancel方法,并將參數(shù)mayInterruptIfRunning設(shè)置為true,那么這個Future 可以中斷正在執(zhí)行任務(wù)的線程。如果你編寫的任務(wù)能夠響應(yīng)中斷,那么當(dāng)它被取消時就可以提前返回。在程序清單9-6給出的任務(wù)中,將輪詢線程的中斷狀態(tài),并且在發(fā)現(xiàn)中斷時提前返回。
??????????????????????????????????????????程序清單9-6 ?????????????????取消一個長時間任務(wù) ???????????
Future<?> ??runningTask=null;//線程封閉
startButton. addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
if (runningTask !=nul1){
runningTask =backgroundExec. submit(new Runnable(){
public void run(){
while (moreWork()){
if (Thread. currentThread(). isInterrupted()){
cleanUpPartialWork();
break;
}
doSomeWork();
}
}
} ) ?;
} ?;
} } ) ?;
cancelButton. addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent event){
if (runningTask !=null)
runningTask. cancel(true);
} } ) ?;
???????????????????????????????????????????????????????????????????????
由于runningTask被封閉在事件線程中,因此在對它進(jìn)行設(shè)置或檢查時不需要同步,并且“開始”按鈕的監(jiān)聽器可以確保每次只有一個后臺任務(wù)在運行。然而,當(dāng)任務(wù)完成時最好能通知按鈕監(jiān)聽器,例如說可以禁用“取消”按鈕。我們將在下一節(jié)解決這個問題。
?進(jìn)度標(biāo)識和完成標(biāo)識
通過Future 來表示一個長時間的任務(wù),可以極大地簡化取消操作的實現(xiàn)。在FutureTask中也有一個done 方法同樣有助于實現(xiàn)完成通知。當(dāng)后臺的Callable 完成后,將調(diào)用done。通過done 方法在事件線程中觸發(fā)一個完成任務(wù),我們能夠構(gòu)造一個BackgroundTask類,這個類將提供一個在事件線程中調(diào)用的onCompletion方法,如程序清單9-7所示。
?????????????????????程序清單9-7支持取消,完成通知以及進(jìn)度通知的后臺任務(wù)類?????????????????????
abstract class BackgroundTask<V>implements Runnable, Future<V>{
private final FutureTask<V>computation =new Computation();
private class Computation extends FutureTask<V>{
public Computation(){
super(new Callable<V>(){
public v call() throws Exception {
return BackgroundTask. this. compute();
}
} ) ?;
}
protected final void done(){
GuiExecutor. instance(). execute(new Runnable(){
public void run(){
V value =null;
Throwable thrown =null;
boolean cancelled =false;
try {
value ?=get();
}catch (ExecutionException e){
thrown =e. getCause();
}catch (CancellationException e){
cancelled =true;
}catch (InterruptedException consumed){
}finally {
onCompletion(value, thrown, cancelled);
}
} ?;
} ) ?;
}
}
protected void setProgress(final int current, final int max){
GuiExecutor. instance(). execute(new Runnable(){
public void run(){onProgress(current, max);}
} ) ;
}
// ??在后臺線程中被取消
protected abstract v compute() throws Exception;
// ??在事件線程中被取消
protected void onCompletion(V result, Throwable exception,
boolean cancelled){}
protected void onProgress(int current, int max){}
// ??Future的其他方法
}
????????????????????????????????????????????????????????????????????
BackgroundTask還支持進(jìn)度標(biāo)識。compute 方法可以調(diào)用setProgress方法以數(shù)字形式來指示進(jìn)度。因而在事件線程中調(diào)用onProgress,從而更新用戶界面以顯示可視化的進(jìn)度信息。
要想實現(xiàn)BackgroundTask,你只需要實現(xiàn)compute,該方法將在后臺線程中調(diào)用。也可以改寫onCompletion和onProgress,這兩個方法也會在事件線程中調(diào)用。
基于FutureTask構(gòu)造的BackgroundTask 還能簡化取消操作。Compute不會檢查線程的中斷狀態(tài),而是調(diào)用Future. isCancelled。程序清單9-8 通過BackgroundTask重新實現(xiàn)了程序清單9-6中的示例程序。
???????????????程序清單9-8 ??通過BackgroundTask來執(zhí)行長時間的并且可取消的任務(wù) ??????????????
startButton. addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
class CancelListener implements ActionListener {
BackgroundTask<?>task;
public void actionPerformed(ActionEvent event){
if (task !=null)
task. cancel(true);
}
}
final CancelListen èr listener =new CancelListener();
listener. task =new BackgroundTask<Void>(){
public Void compute(){
while (moreWork()&&lisCancelled())
doSomeWork();
return null;
}
public void onCompletion(boolean cancelled, string s,
Throwable exception){
cancelButton. removeActionListener(listener);
label. setText("done");
}
} ?;
cancelButton. addActionListener(listener);
backgroundExec. execute(listener. task);
}
} ?) ?;
??????????????????????????????????????????????????????????????????????
SwingWorker文章來源:http://www.zghlxwxcb.cn/news/detail-476314.html
我們已經(jīng)通過FutureTask和Executor 構(gòu)建了一個簡單的框架,它會在后臺線程中執(zhí)行長時間的任務(wù),因此不會影響GUI的響應(yīng)性。在任何單線程的GUI框架都可以使用這些技術(shù),而不僅限于Swing。在Swing中,這里給出的許多特性是由SwingWorker類提供的,包括取消、完成通知、進(jìn)度指示等。在《The Swing Connection》和《The Java Tutorial》等資料中介紹了不同版本的SwingWorker,并在Java 6中包含了一個更新后的版本。文章來源地址http://www.zghlxwxcb.cn/news/detail-476314.html
到了這里,關(guān)于長時間的GUI任務(wù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!