天天看點

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

本文将讨論并發機制在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類。這個類有許多有用的特性,包括在工作線程任務與其他線程任務之間的通信與協作。 

<b>1</b><b>.初始線程</b><b></b>

每個程式都會在應用邏輯開始時生成一系列的線程。在标準的程式中,隻有一個這樣的線程:這個線程将調用程式主類中的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元件的代碼必須在事件派發線程中執行。這個限制将在下文中讨論。

<b> 2</b><b>.事件派發線程</b><b></b>

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

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

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

<b> 3</b><b>.工作線程與</b><b>SwingWorker</b>

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

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

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

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

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

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

<b>4</b><b>.簡單的背景任務</b><b></b>

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

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

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

SwingWorker worker = new SwingWorker&lt;ImageIcon[], Void&gt;() {

    @Override

    public ImageIcon[] doInBackground() {

        final ImageIcon[] innerImgs = new ImageIcon[nimgs];

        for (int i = 0; i &lt; nimgs; i++) {

            innerImgs[i] = loadImage(i+1);

        }

        return innerImgs;

    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。 

<b>5</b><b>.具有中間結果的任務</b><b></b>

讓一個正在工作的背景任務提供中間結果是很有用處的。背景任務可以調用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&lt;Void, FlipPair&gt; {

因為任務沒有傳回一個最終結果,這裡不需要指定第一個類型參數是什麼,使用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));

<b> 6</b><b>.取消背景任務</b><b></b>

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

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

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

<b> 7</b><b>.綁定屬性和狀态方法</b><b></b>

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。

本文轉自zhangjunhd51CTO部落格,原文連結:http://blog.51cto.com/zhangjunhd/34727,如需轉載請自行聯系原作者