SwingWorker

     Swing應用程序員常見的錯誤是誤用Swing事件調度線程(Event DispatchThread,EDT)。他們要麼從非UI線程訪問UI組件;要麼不考慮事件執行順序;要麼不使用獨立任務線程而在EDT線程上執行耗時任務,結果使編寫的應用程序變得響應遲鈍、速度很慢。耗時計算和輸入/輸出(IO)密集型任務不該放在SwingEDT上運行。發現這種問題的代碼並不容易,但Java SE6提供了javax.swing.SwingWorker類,使修正這種代碼變得更容易。java

        使用SwingWorker,程序能啓動一個任務線程來異步查詢,並立刻返回EDT線程,容許EDT繼續執行後續的UI事件。程序員

 

        SwingWorker類幫你管理任務線程和EDT之間的交互,儘管SwingWorker不能解決併發線程中遇到的全部問題,但的確有助於分離SwingEDT和任務線程,使它們各負其責:對於EDT來講,就是繪製和更新界面,並響應用戶輸入;對於任務線程來講,就是執行和界面無直接關係的耗時任務和I/O密集型操做。安全

 

SwingWorker結構

       SwingWoker實現了java.util.concurrent.RunnableFuture接口。RunnableFuture接口是Runnable和Future兩個接口的簡單封裝。併發

由於實現了Runnable,因此有run方法,調用FutureTask.run()異步

由於實現了Future,因此有:async

 

public abstract class SwingWorker<T, V> implements RunnableFuture<T> {  
    //FutureTask  
    private final FutureTask<T> future;  
    public final void run() {  
        future.run();  
    }  
  
    public SwingWorker() {  
        Callable<T> callable =   
                new Callable<T>() {  
//1. 任務線程一建立就處於PENDING狀態  
                    public T call() throws Exception {  
//2. 當doInBackground方法開始時,任務線程就進入STARTED狀態  
                    setState(StateValue.STARTED);    
                        return doInBackground();  
                    }  
                };  
  
        //FutureTask  
        future = new FutureTask<T>(callable) {  
  
                       @Override  
                       protected void done() {  
                           doneEDT();  
//3. 當doInBackground方法完成後,任務線程就處於DONE狀態  
                           setState(StateValue.DONE);   
                       }  
                   };  
  
       state = StateValue.PENDING;  
       propertyChangeSupport = new SwingWorkerPropertyChangeSupport(this);  
       doProcess = null;  
       doNotifyProgressChange = null;  
    }  
  
}  

       SwingWorker有兩個類型參數:T及VT是doInBackground和get方法的返回類型;V是publish和process方法要處理的數據類型ide

       SwingWorker實例不可複用,每次執行任務必須生成新的實例。函數

 

doInBackground和get和done

//一、doInBackground  
protected abstract T doInBackground() throws Exception ;  
//二、get --不可重寫  
public final T get() throws InterruptedException, ExecutionException {  
    return future.get();  
}  
//三、done  
protected void done() {  
}  
  
  
/** 
 * Invokes {@code done} on the EDT. 
 */  
private void doneEDT() {  
    Runnable doDone =   
        new Runnable() {  
            public void run() {  
                done();  
            }  
        };   
    //SwingWorker在EDT上激活done()  
    if (SwingUtilities.isEventDispatchThread()) {   
        doDone.run();  
    } else {  
        doSubmit.add(doDone);  
    }  
}  

 

       doInBackground方法做爲任務線程的一部分執行,它負責完成線程的基本任務,並以返回值來做爲線程的執行結果。繼承類須覆蓋該方法並確保包含或代理任務線程的基本任務。不要直接調用該方法,應使用任務對象的execute方法來調度執行。this

       在得到執行結果後應使用SwingWorker的get方法獲取doInBackground方法的結果。能夠在EDT上調用get方法,但該方法將一直處於阻塞狀態,直到任務線程完成。最好只有在知道結果時才調用get方法,這樣用戶便不用等待。爲防止阻塞,可使用isDone方法來檢驗doInBackground是否完成。另外調用方法get(longtimeout, TimeUnitunit)將會一直阻塞直到任務線程結束或超時。get獲取任務結果的最好地方是在done方法內spa

       在doInBackground方法完成以後,SwingWorker調用done方法。若是任務須要在完成後使用線程結果更新GUI組件或者作些清理工做,可覆蓋done方法來完成它們。這兒是調用get方法的最好地方,由於此時已知道線程任務完成了,SwingWorker在EDT上激活done方法,所以能夠在此方法內安全地和任何GUI組件交互

 

SwingWorker testWorker = new SwingWorker<Icon , Void>(){  
      @Override  
       protected Icon doInBackground() throws Exception {  
        Icon icon = retrieveImage(strImageUrl);   
            return icon;   
       }   
  
       protected void done(){   
       //沒有必要用invokeLater!由於done()自己是在EDT中執行的   
           SwingUtilities.invokeLater(new Runnable(){   
            @Override   
            public void run() {  
            Icon icon= get();  
                        lblImage.setIcon(icon); //lblImage可經過構造函數傳入  
                }             
}  
//execute方法是異步執行,它當即返回到調用者。在execute方法執行後,EDT當即繼續執行  
testWorker.execute();  

 

  • 指定Icon做爲doInBackground和get方法的返回類型
  • 由於並不產生任何中間數據,因此指定Void類型做爲中間結果類型。

 

publish和process

       SwingWorker在doInBackground方法結束後才產生最後結果,但任務線程也能夠產生和公佈中間數據。有時不必等到線程完成就能夠得到中間結果。

      中間結果是任務線程在產生最後結果以前就能產生的數據。當任務線程執行時,它能夠發佈類型爲V的中間結果,經過覆蓋process方法來處理中間結果。

      任務對象的父類會在EDT線程上激活process方法,所以在process方法中程序能夠安全的更新UI組件

 

   
//SwingWorker.publish  
   protected final void publish(V... chunks) {  
        synchronized (this) {  
            if (doProcess == null) {  
                doProcess = new AccumulativeRunnable<V>() {  
                    @Override  
                    public void run(List<V> args) {  
                        //調用process  
                        process(args);   
                    }  
                    @Override  
                    protected void submit() {  
                        doSubmit.add(this);  
                    }  
                };  
            }  
        }  
        doProcess.add(chunks);  
    }  
  
    //SwingWorker.process 在EDT中調用  
    protected void process(List<V> chunks) {  
    }  

 

當從任務線程調用publish方法時,SwingWorker類調度process方法。有意思的是process方法是在EDT上面執行,這意味着能夠同Swing組件和其模型直接交互。

 

例如可以讓publish處理Icon類型的數據;則doInBackground對應應該返回List<Icon>類型

       使用publish方法來發布要處理的中間數據,當ImageSearcher線程下載縮略圖時,它會隨着下載而更新圖片信息列表,還會發布每一批圖像信息,以便UI能在圖片數據到達時顯示這些圖片。

       若是SwingWorker經過publish發佈了一些數據,那麼也應該實現process方法來處理這些中間結果,任務對象的父類會在EDT線程上激活process方法,所以在此方法中程序能夠安全的更新UI組件。

private void retrieveAndProcessThumbnails(List<ImageInfo> infoList) {  
  for (int x=0; x <infoList.size() && !isCancelled(); ++x) {             
    ImageInfo info = infoList.get(x);  
    String strImageUrl = String.format("%s/%s/%s_%s_s.jpg",  
    IMAGE_URL, info.getServer(), info.getId(), info.getSecret());  
    Icon thumbNail = retrieveThumbNail(strImageUrl);  
    info.setThumbnail(thumbNail);  
    //發佈中間結果  
    publish(info);   
    setProgress(100 * (x+1)/infoList.size());  
  }  
}     
/** 
 * Process is called as a result of this worker thread's calling the 
 * publish method. This method runs on the event dispatch thread. 
 * 
 * As image thumbnails are retrieved, the worker adds them to the 
 * list model. 
 * 
 */  
@Override  
protected void process(List<ImageInfo> infoList) {  
  for(ImageInfo info: infoList) {  
    if (isCancelled()) { //見下節  
      break;  
    }  
    //處理中間結果  
    model.addElement(info);  
  }       
}  

 

cancel和isCancelled

public final boolean cancel(boolean mayInterruptIfRunning) {  
    return future.cancel(mayInterruptIfRunning);  
}  
  
/** 
 * {@inheritDoc} 
 */  
public final boolean isCancelled() {  
    return future.isCancelled();  
}  

 

若是想容許程序用戶取消任務,實現代碼要在SwingWorker子類中週期性地檢查取消請求。調用isCancelled方法來檢查是否有取消請求。檢查的時機主要是:

  • doInBackground方法的子任務在獲取每一個縮略圖以前
  • process方法中在更新GUI列表模型以前
  • done方法中在更新GUI列表模型最終結果以前

【例】判斷是否被取消(見上例)

【例】取消

能夠經過調用其cancel方法取消SwingWorker線程

private void searchImages(String strSearchText, int page) {  
  
  if (searcher != null && !searcher.isDone()) {  
    // Cancel current search to begin a new one.  
    // You want only one image search at a time.  
    //檢查現有線程是否正在運行,若是正在運行則調用cancel來取消  
    searcher.cancel(true);  
    searcher = null;  
  }  
  ...  
  // Provide the list model so that the ImageSearcher can publish  
  // images to the list immediately as they are available.  
  searcher = new ImageSearcher(listModel, API_KEY, strEncodedText, page);  
  searcher.addPropertyChangeListener(listenerMatchedImages);  
  progressMatchedImages.setIndeterminate(true);  
  // Start the search!  
  searcher.execute();  
  // This event thread continues immediately here without blocking.  
}   

 

setProgress和getProgress

任務對象有一個進度屬性,隨着任務進展時,能夠將這個屬性從0更新到100標識任務進度。當你在任務實例內處理這些信息時,你能夠調用setProgress方法來更新這個屬性。

當該屬性發生變化時,任務通知處理器進行處理。(?)

//javax.imageio.ImageReader reader  
  
reader.addIIOReadProgressListener(new IIOReadProgressListener() {  
  ...             
  public void imageProgress(ImageReader source, float percentageDone) {  
    setProgress((int) percentageDone);  
  }  
  public void imageComplete(ImageReader source) {  
    setProgress(100);  
  }  
});   

 

客戶端調用一、更新進度條事件處理

/** 
 * ProgressListener listens to "progress" property changes  
   in the SwingWorkers that search and load images. 
 */  
class ProgressListener implements PropertyChangeListener {  
  // Prevent creation without providing a progress bar.  
  private ProgressListener() {}    
  ProgressListener(JProgressBar progressBar) {  
    this.progressBar = progressBar;  
    this.progressBar.setValue(0);  
  }    
  public void propertyChange(PropertyChangeEvent evt) {  
    String strPropertyName = evt.getPropertyName();  
    if ("progress".equals(strPropertyName)) {  
      progressBar.setIndeterminate(false);  
      int progress = (Integer)evt.getNewValue();  
      progressBar.setValue(progress);  
    }  
  }    
  private JProgressBar progressBar;  
}   

客戶端調用二、添加監聽

 

private void listImagesValueChanged(ListSelectionEvent evt) {  
  ...  
  ImageInfo info = (ImageInfo) listImages.getSelectedValue();  
  String id = info.getId();  
  String server = info.getServer();  
  String secret = info.getSecret();  
  // No need to search an invalid thumbnail image  
  if (id == null || server == null || secret == null) {  
    return;  
  }  
  String strImageUrl = String.format(IMAGE_URL_FORMAT, server, id, secret);  
  retrieveImage(strImageUrl);  
  ...  
}      
private void retrieveImage(String imageUrl) {  
  // SwingWorker,不可複用  
  ImageRetriever imgRetriever = new ImageRetriever(lblImage, imageUrl);  
  progressSelectedImage.setValue(0);  
  // Listen for changes in the "progress" property.  
  // You can reuse the listener even though the worker thread will be a new SwingWorker.  
  imgRetriever.addPropertyChangeListener(listenerSelectedImage);    
  progressSelectedImage.setIndeterminate(true);    
  // Tell the worker thread to begin with this asynchronous method.  
  imgRetriever.execute();  
  // This event thread continues immediately here without blocking.    
}       
相關文章
相關標籤/搜索