程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Swing中的並發-使用SwingWorker線程模式

Swing中的並發-使用SwingWorker線程模式

編輯:關於JAVA

本文介紹Java SE 6中的SwingWorker線程工作模式,翻譯Concurrency in Swing(http://java.sun.com/docs/books/tutorial/uiswing/concurrency/index.html)。

author: ZJ 2007-7-16

Blog: http://zhangjunhd.blog.51cto.com/

本文將討論並發機制在Swing編程中的應用。

謹慎地使用並發機制對Swing開發人員來說非常重要。一個好的Swing程序使用並發機制來創建不會失去響應的用戶接口-不管是什麼樣的用戶交互,程序總能夠對其給出響應。創建一個有響應的程序,開發人員必須學會如何在Swing框架中使用多線程。

一個Swing開發人員將會與下面幾類線程打交道:

[1]Initial threads(初始線程),此類線程將執行初始化應用代碼。

[2]The event dispatch thread(事件派發線程),所有的事件處理代碼在這裡執行。大多數與Swing框架交互的代碼也必須執行這個線程。

[3]Worker threads(工作線程),也稱作background threads(後台線程),此類線程將執行所有消耗時間的任務。

開發人員不需要在代碼中顯式的創建這些線程:它們是由runtime或Swing框架提供的。開發人員的工作就是利用這些線程來創建具有響應的,持久的Swing程序。

如同所有其他在Java平台上運行的程序,一個Swing程序可以創建額外的線程和線程池,這需要使用本文即將介紹的方法。本文將介紹以上這三種線程。工作線程的討論將涉及到使用javax.swing.SwingWorker類。這個類有許多有用的特性,包括在工作線程任務與其他線程任務之間的通信與協作。

1.初始線程

每個程序都會在應用邏輯開始時生成一系列的線程。在標准的程序中,只有一個這樣的線程:這個線程將調用程序主類中的main方法。在applet中初始線程是applet對象的構造子,它將調用init方法;這些actions可能在一個單一的線程中執行,或在兩個或三個不同的線程中,這些都依據Java平台的具體實現。在本文中,我們稱這類線程為初始線程(initial threads)。

在Swing程序中,初始線程沒有很多事情要做。它們最基本的任務是創建一個Runnable對象,用於初始化GUI以及為那些用於執行事件派發線程中的事件的對象編排順序。一旦GUI被創建,程序將主要由GUI事件驅動,其中的每個事件驅動將引起一個在事件派發線程中事件的執行。程序代碼可以編排額外的任務給事件驅動線程(前提是它們會被很快的執行,這樣才不會干擾事件的處理)或創建工作線程(用於執行消耗時間的任務)。

一個初始線程編排GUI創建任務是通過調用javax.swing.SwingUtilities.invokeLater或javax.swing.SwingUtilities.invokeAndWait。這兩個方法都帶有一個唯一的參數:Runnable用於定義新的任務。它們唯一的區別是:invokerLater僅僅編排任務並返回;invokeAndWait將等待任務執行完畢才返回。

看下面示例:

SwingUtilities.invokeLater(new Runnable()) {
   public void run() {
     createAndShowGUI();
   }
}

在applet中,創建GUI的任務必須被放入init方法中並且使用invokeAndWait;否則,初始過程將有可能在GUI創建完之前完成,這樣將有可能出現問題。在其他的情況下,編排GUI創建任務通常是初始線程中最後一個被執行的,所以使用invokeLater或invokeAndWait都可以。

為什麼初始線程不直接創建GUI?因為幾乎所有的用於創建和交互Swing組件的代碼必須在事件派發線程中執行。這個約束將在下文中討論。

2.事件派發線程

Swing事件的處理代碼在一個特殊的線程中執行,這個線程被稱為事件派發線程。大部分調用Swing方法的代碼都在這個線程中被執行。這樣做是必要的,因為大部分Swing對象是“非線程安全的”。

可以將代碼的執行想象成在事件派發線程中執行一系列短小的任務。大部分任務被事件處理方法調用,諸如ActionListener.actionPerformed。其余的任務將被程序代碼編排,使用invokeLater或invokeAndWait。在事件派發線程中的任務必須能夠被快速執行完成,如若不然,未經處理的事件被積壓,用戶界面將變得“響應遲鈍”。

如果你需要確定你的代碼是否是在事件派發線程中執行,可調用javax.swing.SwingUtilities.isEventDispatchThread。

3.工作線程與SwingWorker

當一個Swing程序需要執行一個長時間的任務,通常將使用一個工作線程來完成。每個任務在一個工作線程中執行,它是一個javax.swing.SwingWorker類的實例。SwingWorker類是抽象類;你必須定義它的子類來創建一個SwingWorker對象;通常使用匿名內部類來這做這些。

SwingWorker提供一些通信與控制的特征:

[1]SwingWorker的子類可以定義一個方法,done。當後台任務完成的時候,它將自動的被事件派發線程調用。

[2]SwingWorker類實現java.util.concurrent.Future。這個接口允許後台任務提供一個返回值給其他線程。該接口中的方法還提供允許撤銷後台任務以及確定後台任務是被完成了還是被撤銷的功能。

[3]後台任務可以通過調用SwingWorker.publish來提供中間結果,事件派發線程將會調用該方法。

[4]後台任務可以定義綁定屬性。綁定屬性的變化將觸發事件,事件派發線程將調用事件處理程序來處理這些被觸發的事件。

4.簡單的後台任務

下面介紹一個示例,這個任務非常簡單,但它是潛在地消耗時間的任務。TumbleItem applet導入一系列的圖片文件。如果這些圖片文件是通過初始線程導入的,那麼將在GUI出現之前有一段延遲。如果這些圖片文件是在事件派發線程中導入的,那麼GUI將有可能出現臨時無法響應的情況。

為了解決這些問題,TumbleItem類在它初始化時創建並執行了一個StringWorker類的實例。這個對象的doInBackground方法,在一個工作線程中執行,將圖片導入一個ImageIcon數組,並且返回它的一個引用。接著done方法,在事件派發線程中執行,得到返回的引用,將其放在applet類的成員變量imgs中。這樣做可以允許TumbleItem類立刻創建GUI,而不必等待圖片導入完成。

下面的示例代碼定義和實現了一個SwingWorker對象。

SwingWorker worker = new SwingWorker<ImageIcon[], Void>() {
   @Override
   public ImageIcon[] doInBackground() {
     final ImageIcon[] innerImgs = new ImageIcon[nimgs];
     for (int i = 0; i < nimgs; i++) {
       innerImgs[i] = loadImage(i+1);
     }
     return innerImgs;
   }
   @Override
   public void done() {
     //Remove the "Loading images" label.
     animator.removeAll();
     loopslot = -1;
     try {
       imgs = get();
     } catch (InterruptedException ignore) {}
     catch (java.util.concurrent.ExecutionException e) {
       String why = null;
       Throwable cause = e.getCause();
       if (cause != null) {
         why = cause.getMessage();
       } else {
         why = e.getMessage();
       }
       System.err.println("Error retrieving file: " + why);
     }
   }
};

所有的繼承自SwingWorker的子類都必須實現doInBackground;實現done方法是可選的。

注意,SwingWorker是一個范型類,有兩個參數。第一個類型參數指定doInBackground的返回類型。同時也是get方法的類型,它可以被其他線程調用以獲得來自於doInBackground的返回值。第二個類型參數指定中間結果的類型,這個例子沒有返回中間結果,所以設為void。

使用get方法,可以使對象imgs的引用(在工作線程中創建)在事件派發線程中得到使用。這樣就可以在線程之間共享對象。

實際上有兩個方法來得到doInBackground類返回的對象。

[1]調用SwingWorker.get沒有參數。如果後台任務沒有完成,get方法將阻塞直到它完成。

[2]調用SwingWorker.get帶參數指定timeout。如果後台任務沒有完成,阻塞直到它完成-除非timeout期滿,在這種情況下,get將拋出java.util.concurrent.TimeoutException。

5.具有中間結果的任務

讓一個正在工作的後台任務提供中間結果是很有用處的。後台任務可以調用SwingWorker.publish方法來做到這個。這個方法接受許多參數。每個參數必須是由SwingWorker的第二個類型參數指定的一種。

可以覆蓋(override)SwingWorker.process來保存由publish方法提供的結果。這個方法是由事件派發線程調用的。來自publish方法的結果集通常是由一個process方法收集的。

我們看一下Filpper.java提供的實例。這個程序通過一個後台任務產生一系列的隨機布爾值測試java.util.Random。就好比是一個投硬幣試驗。為了報告它的結果,後台任務使用了一個對象FlipPair。

private static class FlipPair {
   private final long heads, total;
   FlipPair(long heads, long total) {
     this.heads = heads;
     this.total = total;
   }
}

heads表示true的結果;total表示總的投擲次數。

後台程序是一個FilpTask的實例:

private class FlipTask extends SwingWorker<Void, FlipPair> {

因為任務沒有返回一個最終結果,這裡不需要指定第一個類型參數是什麼,使用Void。在每次“投擲”後任務調用publish:

@Override

protected Void doInBackground() {
   long heads = 0;
   long total = 0;
   Random random = new Random();
   while (!isCancelled()) {
     total++;
     if (random.nextBoolean()) {
       heads++;
     }
     publish(new FlipPair(heads, total));
   }
   return null;
}

由於publish時常被調用,許多的FlipPair值將在process方法被事件派發線程調用之前被收集;process僅僅關注每次返回的最後一組值,使用它來更新GUI:

protected void process(List pairs) {
   FlipPair pair = pairs.get(pairs.size() - 1);
   headsText.setText(String.format("%d", pair.heads));
   totalText.setText(String.format("%d", pair.total));
   devText.setText(String.format("%.10g",
       ((double) pair.heads)/((double) pair.total) - 0.5));
}

6.取消後台任務

調用SwingWorker.cancel來取消一個正在執行的後台任務。任務必須與它自己的撤銷機制一致。有兩個方法來做到這一點:

[1]當收到一個interrupt時,將被終止。

[2]調用SwingWorker.isCanceled,如果SwingWorker調用cancel,該方法將返回true。

7.綁定屬性和狀態方法

SwingWorker支持bound properties,這個在與其他線程通信時很有作用。提供兩個綁定屬性:progress和state。progress和state可以用於觸發在事件派發線程中的事件處理任務。

通過實現一個property change listener,程序可以捕捉到progress,state或其他綁定屬性的變化。

7.1The progress Bound Variable

Progress綁定變量是一個整型變量,變化范圍由0到100。它預定義了setter (the protected SwingWorker.setProgress)和getter (the public SwingWorker.getProgress)方法。

7.2The state Bound Variable

State綁定變量的變化反映了SwingWorker對象在它的生命周期中的變化過程。該變量中包含一個SwingWorker.StateValue的枚舉類型。可能的值有:

[1]PENDING

這個狀態持續的時間為從對象的建立知道doInBackground方法被調用。

[2]STARTED

這個狀態持續的時間為doInBackground方法被調用前一刻直到done方法被調用前一刻。

[3]DONE

對象存在的剩余時間將保持這個狀態。

需要返回當前state的值可調用SwingWorker.getState。

7.3Status Methods

兩個由Future接口提供的方法,同樣可以報告後台任務的狀態。如果任務被取消,isCancelled返回true。此外,如果任務完成,即要麼正常的完成,要麼被取消,isDone返回true。

附件下載:

TumbleItem.java:

http://zhangjunhd.blog.51cto.com/attachment/200707/113473_1184854082.txt

Flipper.java:

http://zhangjunhd.blog.51cto.com/attachment/200707/113473_1184854083.txt

ProgressBarDemo.java:

http://zhangjunhd.blog.51cto.com/attachment/200707/113473_1184854084.txt

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved