2011年9月11日 星期日

Java Swing 的多執行緒(二)

執行緒有點複雜。

3、工人執行緒和SwingWorker

當Swing程式需要長時間的工作時,通常是使用Worker Thread(工人執行緒),也就是背景執行緒,由工人執行緒執行的工作,都是javax.swing.SwingWorker的實體,SwingWorker是虛擬類別,匿名內層類別可以很方便的建立SwingWorker物件。

SwingWorker提供一些有用的特性:

  • SwingWorker子類別可以定義done()方法,當背景工作完成後,EDT會自動呼叫這個方法。
  • SwingWorker實作java.util.concurrent.Future,這個介面允許背景程式提供一個傳回值給其他的執行緒,這個介面的其他方法,可以取消背景工作,或偵測背景工作正在執行或被取消了。
  • 藉由呼叫SwingWorker.publish(),背景工作可以提供中間結果,呼叫方法可以使EDT去呼叫SwingWorker.process()方法。
  • 背景工作可以定義綁定的屬性,當綁定的屬性改變時,會觸發事件,造成EDT去執行處理事件的方法。

所以可以得知,當SwingWorker的執行緒在執行背景工作時,有很多的時機會可以觸發EDT去執行SwingWorker的方法,或是屬性改變事件處理方法。

另外,SwingWorker物件是不可重用的,每次執行都必須建立一個新的實體。

4、背景工作範例

下面的範例是背景載入圖片:

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()方法,要提醒的是,down()方法是由EDT所執行。

SwingWorker是泛型型別,有2個型別參數,第一個參數指定doInBackground()方法返回的型別,也是get()方法返回的型別,其他的執行緒,可以透過get()方法,取得doInBackground()所傳回的物件。第2個型別參數,是當doInBackground()還在執行時,所傳回的暫時性物件。範例沒有傳回值,所以是Void型別。

另外,為什麼要用done()方法設定imgs,而不直接在doInBackground()方法中設定,因為imgs參考到在工人執行緖建立的物件,而要在EDT使用這些物件。當不同執行緖共享物件時,要確定物件被一個執行緒改變時,另外一個執行緒要能得知改變,就要使用這樣的方法。使用get()方法可以保證這樣的狀況,這是因為get()方法會在建立imgs的程式碼和使用imgs的程式碼之間,建立「在之前發生」(happen-before)關係。

有2種方法可以取得doInBackground()所傳回的物件:


  • SwingWorker.get(),且沒有參數,如果doInBackground()還沒結束,執會停住,等到結束。

  • SwingWorker.get(),並傳入一個timeout參數,如果doInBackground()還沒結束,會停住等待,或是等到timeout,並丟出java.util.concurrent.TimeoutException。

要注意,如果在EDT中呼叫get()方法,GUI會停住,直到get()方法返回。所以確定背景工作完成後,再呼叫get()方法。

2011年9月6日 星期二

Java Swing的多執行緒(一)

在研究NetBeans的過程式,突然需要用到多執行緒的功能,對於專業程式設計師而言,多執行緒的程式可能稀鬆平常,但對於半調子的我,可從來沒有深入研究過。
為求速成,只好求助官方教學檔Concurrency in Swing,後續的內容都是從官方教學中精減出來,主要是快速讓自己能對Java 多執行緒有基本的瞭解。

1、Swing多執行緒

一個Swing程式,包含3種執行緖。一個是Initial threads(初始執行緖),用於執行應用程式一開始的程式碼。另外一個是event dispatch thread(EDT)(事件分派執行緖),所有事件處理的程式碼都是這個執行緖執行,大多數和Swing框架互動的程式碼也是有這個執行緖所執行。再來是Worker thread(工人執行緖)也稱為background thread(背景執行緖),比較耗時的背景工作都由這個執行緖執行。
程式設計師並不需要提供程式碼明確的建立這些執行緖,它們由Swing框架或執行時期容器所提供,程式設計師只需要使用這些執行緖建立互動及可維護的Swing應用程式。

2、初始執行緖

每個應用程式一開始都會有一組初始的執行緒,對於普通Java程式,只有一個初始執行緒,用於執行main()方法,對於applet而言,初始執行緒會建立applet物件並呼叫init()和start()方法,這些初始執行緒可能是一個也可能是多個,要視Java執行時期容器的實作而定。
對於Swing應用程式,初始執行緖最基本的工作是建立一個Runable物件,這個物件用於初始化GUI,並排程由EDT所執行的物件。一但GUI建立後,應用程式主要由GUI事件驅動,這些事件是由EDT所執行的短暫工作,應用程式可以排程EDT所執行的工作,由EDT所執行的工作應該是越短越好,也可以執行較長的工作,也就是工人執行緖。
初始執行緖透過javax.swing.SwingUtilities.invokeLater() 或 javax.swing.SwingUtilities.invokeAndWait()等2個方法,處理建立GUI的工作,這2個方法都只有一個參數,一個Runnable物件,這個2方法的不同處是,invokeLater()只排程工作然後就返回(return),而invokeAndWait()會等到工作完成再返回。

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

對於applet而言,GUI的工作是由init()方法透過invokeAndWait()所建立,如果不這樣做,init()可能會在GUI建立之前返回,會造成瀏覽器啟動applet會有問題,而對於一般的GUI程式,建立GUI物件通常是初始執行緖所執行的唯一工作,所以使用invokeLater()或invokeAndWait()都可以。
為什麼初始執行緖只建立GUI物件,因為所有建立及與Swing元件互動的程式碼都是透過EDT所執行,這是很重要的限制。