Java Swing编程中需要注意的线程调度及SwingWorker的用法

         Java Swing 被设计成了一个单线程模型,这有很多原因,包括开发成本和同步Swing的复杂性--这都会造成一个迟钝的API。为了达到单线程模型,有一个专门的线程用于和Swing组件交互,就是Swing事件调度线程(Event DispatchThread,EDT)。所以在Swing中执行耗时任务时,要在一个新线程中执行,不能阻塞EDT线程,否则会造成swing界面的不响应,即卡死。SwingWorker就是用来管理任务线程和EDT之间调度的一个工具类。

       

        API中的定义:

       

javax.swing.SwingWorker< Boolean, String>

An abstract class to perform lengthy GUI-interacting tasks in a dedicated thread.

When writing a multi-threaded application using Swing, there are two constraints to keep in mind: (refer to How to Use Threads for more details):

  • Time-consuming tasks should not be run on the Event Dispatch Thread. Otherwise the application becomes unresponsive.
  • Swing components should be accessed on the Event Dispatch Thread only.

These constraints mean that a GUI application with time intensive computing needs at least two threads: 1) a thread to perform the lengthy task and 2) the Event Dispatch Thread (EDT) for all GUI-related activities. This involves inter-thread communication which can be tricky to implement.

SwingWorker is designed for situations where you need to have a long running task run in a background thread and provide updates to the UI either when done, or while processing. Subclasses of SwingWorker must implement the doInBackground method to perform the background computation.

Workflow

There are three threads involved in the life cycle of a SwingWorker :

  • Current thread: The execute method is called on this thread. It schedules SwingWorker for the execution on a worker thread and returns immediately. One can wait for the SwingWorker to complete using the get methods.

  • Worker thread: The doInBackground method is called on this thread. This is where all background activities should happen. To notify PropertyChangeListeners about bound properties changes use the firePropertyChange and getPropertyChangeSupport methods. By default there are two bound properties available: state and progress.

  • Event Dispatch Thread: All Swing related activities occur on this thread. SwingWorker invokes the process and done methods and notifies any PropertyChangeListeners on this thread.

Often, the Current thread is the Event Dispatch Thread.

Before the doInBackground method is invoked on a worker thread, SwingWorker notifies any PropertyChangeListeners about the state property change to StateValue.STARTED. After the doInBackground method is finished the done method is executed. Then SwingWorker notifies any PropertyChangeListeners about the state property change to StateValue.DONE.

SwingWorker is only designed to be executed once. Executing a SwingWorker more than once will not result in invoking the doInBackground method twice.

Sample Usage

The following example illustrates the simplest use case. Some processing is done in the background and when done you update a Swing component.

Say we want to find the "Meaning of Life" and display the result in a JLabel.

   final JLabel label;
   class MeaningOfLifeFinder extends SwingWorker<String, Object> {
        @Override
       public String doInBackground() {
           return findTheMeaningOfLife();
       }

        @Override
       protected void done() {
           try { 
               label.setText(get());
           } catch (Exception ignore) {
           }
       }
   }
 
   (new MeaningOfLifeFinder()).execute();
 

The next example is useful in situations where you wish to process data as it is ready on the Event Dispatch Thread.

Now we want to find the first N prime numbers and display the results in a JTextArea. While this is computing, we want to update our progress in a JProgressBar. Finally, we also want to print the prime numbers to System.out.

 class PrimeNumbersTask extends 
         SwingWorker<List<Integer>, Integer> {
     PrimeNumbersTask(JTextArea textArea, int numbersToFind) { 
         //initialize 
     }

      @Override
     public List<Integer> doInBackground() {
         while (! enough && ! isCancelled()) {
                 number = nextPrimeNumber();
                 publish(number);
                 setProgress(100 * numbers.size() / numbersToFind);
             }
         }
         return numbers;
     }

      @Override
     protected void process(List<Integer> chunks) {
         for (int number : chunks) {
             textArea.append(number + "\n");
         }
     }
 }

 JTextArea textArea = new JTextArea();
 final JProgressBar progressBar = new JProgressBar(0, 100);
 PrimeNumbersTask task = new PrimeNumbersTask(textArea, N);
 task.addPropertyChangeListener(
     new PropertyChangeListener() {
         public  void propertyChange(PropertyChangeEvent evt) {
             if ("progress".equals(evt.getPropertyName())) {
                 progressBar.setValue((Integer)evt.getNewValue());
             }
         }
     });

 task.execute();
 System.out.println(task.get()); //prints all prime numbers we have got
 

Because SwingWorker implements Runnable, a SwingWorker can be submitted to an java.util.concurrent.Executor for execution.

Parameters:
<T> the result type returned by this SwingWorker's doInBackground and get methods
<V> the type used for carrying out intermediate results by this SwingWorker's publish and process methods
Since:
1.6
Version:
1.12 05/14/10
Author:
Igor Kushnirskiy


-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


SwingWorker应用详解:

             From: http://blog.csdn.net/vking_wang/article/details/8994882


               Swing应用程序员常见的错误是误用Swing事件调度线程(Event DispatchThread,EDT)。他们要么从非UI线程访问UI组件;要么不考虑事件执行顺序;要么不使用独立任务线程而在EDT线程上执行耗时任务,结果使编写的应用程序变得响应迟钝、速度很慢。耗时计算和输入/输出(IO)密集型任务不应放在SwingEDT上运行。发现这种问题的代码并不容易,但Java SE6提供了javax.swing.SwingWorker类,使修正这种代码变得更容易。

        使用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,所以有:

Java Swing编程中需要注意的线程调度及SwingWorker的用法_第1张图片

  1. public abstract class SwingWorker<T, V> implements RunnableFuture<T> {  
  2.     //FutureTask  
  3.     private final FutureTask<T> future;  
  4.     public final void run() {  
  5.         future.run();  
  6.     }  
  7.   
  8.     public SwingWorker() {  
  9.         Callable<T> callable =   
  10.                 new Callable<T>() {  
  11. //1. 任务线程一创建就处于PENDING状态  
  12.                     public T call() throws Exception {  
  13. //2. 当doInBackground方法开始时,任务线程就进入STARTED状态  
  14.                     setState(StateValue.STARTED);    
  15.                         return doInBackground();  
  16.                     }  
  17.                 };  
  18.   
  19.         //FutureTask  
  20.         future = new FutureTask<T>(callable) {  
  21.   
  22.                        @Override  
  23.                        protected void done() {  
  24.                            doneEDT();  
  25. //3. 当doInBackground方法完成后,任务线程就处于DONE状态  
  26.                            setState(StateValue.DONE);   
  27.                        }  
  28.                    };  
  29.   
  30.        state = StateValue.PENDING;  
  31.        propertyChangeSupport = new SwingWorkerPropertyChangeSupport(this);  
  32.        doProcess = null;  
  33.        doNotifyProgressChange = null;  
  34.     }  
  35.   
  36. }  

  

       SwingWorker有两个类型参数:T及VT是doInBackground和get方法的返回类型;V是publish和process方法要处理的数据类型

       SwingWorker实例不可复用,每次执行任务必须生成新的实例。


doInBackground和get和done

  1. //1、doInBackground  
  2. protected abstract T doInBackground() throws Exception ;  
  3. //2、get --不可重写  
  4. public final T get() throws InterruptedException, ExecutionException {  
  5.     return future.get();  
  6. }  
  7. //3、done  
  8. protected void done() {  
  9. }  
  10.   
  11.   
  12. /** 
  13.  * Invokes {@code done} on the EDT. 
  14.  */  
  15. private void doneEDT() {  
  16.     Runnable doDone =   
  17.         new Runnable() {  
  18.             public void run() {  
  19.                 done();  
  20.             }  
  21.         };   
  22.     //SwingWorker在EDT上激活done()  
  23.     if (SwingUtilities.isEventDispatchThread()) {   
  24.         doDone.run();  
  25.     } else {  
  26.         doSubmit.add(doDone);  
  27.     }  
  28. }  


       doInBackground方法作为任务线程的一部分执行,它负责完成线程的基本任务,并以返回值来作为线程的执行结果。继承类须覆盖该方法并确保包含或代理任务线程的基本任务。不要直接调用该方法,应使用任务对象的execute方法来调度执行。

       在获得执行结果后应使用SwingWorker的get方法获取doInBackground方法的结果。可以在EDT上调用get方法,但该方法将一直处于阻塞状态,直到任务线程完成。最好只有在知道结果时才调用get方法,这样用户便不用等待。为防止阻塞,可以使用isDone方法来检验doInBackground是否完成。另外调用方法get(longtimeout, TimeUnitunit)将会一直阻塞直到任务线程结束或超时。get获取任务结果的最好地方是在done方法内

       在doInBackground方法完成之后,SwingWorker调用done方法。如果任务需要在完成后使用线程结果更新GUI组件或者做些清理工作,可覆盖done方法来完成它们。这儿是调用get方法的最好地方,因为此时已知道线程任务完成了,SwingWorker在EDT上激活done方法,因此可以在此方法内安全地和任何GUI组件交互

【例】

  1. SwingWorker testWorker = new SwingWorker<Icon , Void>(){  
  2.       @Override  
  3.        protected Icon doInBackground() throws Exception {  
  4.         Icon icon = retrieveImage(strImageUrl);   
  5.             return icon;   
  6.        }   
  7.   
  8.        protected void done(){   
  9.        //没有必要用invokeLater!因为done()本身是在EDT中执行的   
  10.            SwingUtilities.invokeLater(new Runnable(){   
  11.             @Override   
  12.             public void run() {  
  13.             Icon icon= get();  
  14.                         lblImage.setIcon(icon); //lblImage可通过构造函数传入  
  15.                 }             
  16. }  
  17. //execute方法是异步执行,它立即返回到调用者。在execute方法执行后,EDT立即继续执行  
  18. testWorker.execute();  


  • 指定Icon作为doInBackground和get方法的返回类型
  • 因为并不产生任何中间数据,所以指定Void类型作为中间结果类型。


publish和process

       SwingWorker在doInBackground方法结束后才产生最后结果,但任务线程也可以产生和公布中间数据。有时没必要等到线程完成就可以获得中间结果。

      中间结果是任务线程在产生最后结果之前就能产生的数据。当任务线程执行时,它可以发布类型为V的中间结果,通过覆盖process方法来处理中间结果。

      任务对象的父类会在EDT线程上激活process方法,因此在process方法中程序可以安全的更新UI组件

  1.    
  2. //SwingWorker.publish  
  3.    protected final void publish(V... chunks) {  
  4.         synchronized (this) {  
  5.             if (doProcess == null) {  
  6.                 doProcess = new AccumulativeRunnable<V>() {  
  7.                     @Override  
  8.                     public void run(List<V> args) {  
  9.                         //调用process  
  10.                         process(args);   
  11.                     }  
  12.                     @Override  
  13.                     protected void submit() {  
  14.                         doSubmit.add(this);  
  15.                     }  
  16.                 };  
  17.             }  
  18.         }  
  19.         doProcess.add(chunks);  
  20.     }  
  21.   
  22.     //SwingWorker.process 在EDT中调用  
  23.     protected void process(List<V> chunks) {  
  24.     }  

当从任务线程调用publish方法时,SwingWorker类调度process方法。有意思的是process方法是在EDT上面执行,这意味着可以同Swing组件和其模型直接交互。


例如可让publish处理Icon类型的数据;则doInBackground对应应该返回List<Icon>类型

       使用publish方法来发布要处理的中间数据,当ImageSearcher线程下载缩略图时,它会随着下载而更新图片信息列表,还会发布每一批图像信息,以便UI能在图片数据到达时显示这些图片。

       如果SwingWorker通过publish发布了一些数据,那么也应该实现process方法来处理这些中间结果,任务对象的父类会在EDT线程上激活process方法,因此在此方法中程序可以安全的更新UI组件。

【例】

  1. private void retrieveAndProcessThumbnails(List<ImageInfo> infoList) {  
  2.   for (int x=0; x <infoList.size() && !isCancelled(); ++x) {             
  3.     ImageInfo info = infoList.get(x);  
  4.     String strImageUrl = String.format("%s/%s/%s_%s_s.jpg",  
  5.     IMAGE_URL, info.getServer(), info.getId(), info.getSecret());  
  6.     Icon thumbNail = retrieveThumbNail(strImageUrl);  
  7.     info.setThumbnail(thumbNail);  
  8.     //发布中间结果  
  9.     publish(info);   
  10.     setProgress(100 * (x+1)/infoList.size());  
  11.   }  
  12. }     
  13. /** 
  14.  * Process is called as a result of this worker thread's calling the 
  15.  * publish method. This method runs on the event dispatch thread. 
  16.  * 
  17.  * As image thumbnails are retrieved, the worker adds them to the 
  18.  * list model. 
  19.  * 
  20.  */  
  21. @Override  
  22. protected void process(List<ImageInfo> infoList) {  
  23.   for(ImageInfo info: infoList) {  
  24.     if (isCancelled()) { //见下节  
  25.       break;  
  26.     }  
  27.     //处理中间结果  
  28.     model.addElement(info);  
  29.   }       
  30. }  


cancel和isCancelled


  1. public final boolean cancel(boolean mayInterruptIfRunning) {  
  2.     return future.cancel(mayInterruptIfRunning);  
  3. }  
  4.   
  5. /** 
  6.  * {@inheritDoc} 
  7.  */  
  8. public final boolean isCancelled() {  
  9.     return future.isCancelled();  
  10. }  

如果想允许程序用户取消任务,实现代码要在SwingWorker子类中周期性地检查取消请求。调用isCancelled方法来检查是否有取消请求。检查的时机主要是:
  • doInBackground方法的子任务在获取每个缩略图之前
  • process方法中在更新GUI列表模型之前
  • done方法中在更新GUI列表模型最终结果之前

【例】判断是否被取消(见上例)

【例】取消

可以通过调用其cancel方法取消SwingWorker线程

  1. private void searchImages(String strSearchText, int page) {  
  2.   
  3.   if (searcher != null && !searcher.isDone()) {  
  4.     // Cancel current search to begin a new one.  
  5.     // You want only one image search at a time.  
  6.     //检查现有线程是否正在运行,如果正在运行则调用cancel来取消  
  7.     searcher.cancel(true);  
  8.     searcher = null;  
  9.   }  
  10.   ...  
  11.   // Provide the list model so that the ImageSearcher can publish  
  12.   // images to the list immediately as they are available.  
  13.   searcher = new ImageSearcher(listModel, API_KEY, strEncodedText, page);  
  14.   searcher.addPropertyChangeListener(listenerMatchedImages);  
  15.   progressMatchedImages.setIndeterminate(true);  
  16.   // Start the search!  
  17.   searcher.execute();  
  18.   // This event thread continues immediately here without blocking.  
  19. }   


setProgress和getProgress

任务对象有一个进度属性,随着任务进展时,可以将这个属性从0更新到100标识任务进度。当你在任务实例内处理这些信息时,你可以调用setProgress方法来更新这个属性。

当该属性发生变化时,任务通知处理器进行处理。(?)

【例】

  1. //javax.imageio.ImageReader reader  
  2.   
  3. reader.addIIOReadProgressListener(new IIOReadProgressListener() {  
  4.   ...             
  5.   public void imageProgress(ImageReader source, float percentageDone) {  
  6.     setProgress((int) percentageDone);  
  7.   }  
  8.   public void imageComplete(ImageReader source) {  
  9.     setProgress(100);  
  10.   }  
  11. });   

客户端调用1、更新进度条事件处理

  1. /** 
  2.  * ProgressListener listens to "progress" property changes  
  3.    in the SwingWorkers that search and load images. 
  4.  */  
  5. class ProgressListener implements PropertyChangeListener {  
  6.   // Prevent creation without providing a progress bar.  
  7.   private ProgressListener() {}    
  8.   ProgressListener(JProgressBar progressBar) {  
  9.     this.progressBar = progressBar;  
  10.     this.progressBar.setValue(0);  
  11.   }    
  12.   public void propertyChange(PropertyChangeEvent evt) {  
  13.     String strPropertyName = evt.getPropertyName();  
  14.     if ("progress".equals(strPropertyName)) {  
  15.       progressBar.setIndeterminate(false);  
  16.       int progress = (Integer)evt.getNewValue();  
  17.       progressBar.setValue(progress);  
  18.     }  
  19.   }    
  20.   private JProgressBar progressBar;  
  21. }   

客户端调用2、添加监听
  1. private void listImagesValueChanged(ListSelectionEvent evt) {  
  2.   ...  
  3.   ImageInfo info = (ImageInfo) listImages.getSelectedValue();  
  4.   String id = info.getId();  
  5.   String server = info.getServer();  
  6.   String secret = info.getSecret();  
  7.   // No need to search an invalid thumbnail image  
  8.   if (id == null || server == null || secret == null) {  
  9.     return;  
  10.   }  
  11.   String strImageUrl = String.format(IMAGE_URL_FORMAT, server, id, secret);  
  12.   retrieveImage(strImageUrl);  
  13.   ...  
  14. }      
  15. private void retrieveImage(String imageUrl) {  
  16.   // SwingWorker,不可复用  
  17.   ImageRetriever imgRetriever = new ImageRetriever(lblImage, imageUrl);  
  18.   progressSelectedImage.setValue(0);  
  19.   // Listen for changes in the "progress" property.  
  20.   // You can reuse the listener even though the worker thread will be a new SwingWorker.  
  21.   imgRetriever.addPropertyChangeListener(listenerSelectedImage);    
  22.   progressSelectedImage.setIndeterminate(true);    
  23.   // Tell the worker thread to begin with this asynchronous method.  
  24.   imgRetriever.execute();  
  25.   // This event thread continues immediately here without blocking.    
  26. }      

你可能感兴趣的:(Java Swing编程中需要注意的线程调度及SwingWorker的用法)