zoukankan      html  css  js  c++  java
  • 【Java线程】SwingWorker的用法

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

    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. }       
  • 相关阅读:
    【Python】协程
    【设计模式】单例模式
    【Python】闭包和装饰器
    【面试题】Python
    10、Go语言基础之指针
    9、Go语言基础之函数
    8、Go语言基础之map
    15、Python Scrapy Web爬虫框架【3】
    14、Python Scrapy Web爬虫框架【2】
    Redis遇到的坑
  • 原文地址:https://www.cnblogs.com/runningTurtle/p/7098998.html
Copyright © 2011-2022 走看看