为什么在使用多线程appraoch后我的GUI会被冻结?

时间:2012-01-02 15:58:09

标签: java multithreading swing

我使用基于MVC的java创建了一个基于GUI的应用程序。我的应用程序的主要任务是从Internet获取一些数据并开始解析它。

从互联网上获取数据我使用以下课程:

public class WebReader implements Runnable {
    WebRecordResult WRResult = new WebRecordResult ();
    private void    getData(args){
        .
        .
        .
        setResult(result);
    }

    public void run() {
        getData(args);
        WRResult.setResult(getResult());
    }

}


public class WebReaderResult {
    private AtomicReference<String> result = new AtomicReference<>("");

    public String getResult() {
        return result.get();
    }

    public void setResult(String s) {
        result.set(s);
    }
}

在我的控制器内部,我创建了类似的东西:

WebReaderResult wrr = new WebReaderResult();

ExecutorService executor = Executors.newSingleThreadExecutor();
WebReader wr =new WebReader(args);
Future<?> f1 = executor.submit(wr);
executor.shutdown();
try {
    f1.get();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}
wrr.getResult(); 

使用此代码后,我的GUI会被冻结,直到数据可用。我不希望我的GUI被冻结。我该怎么办?

注意:我的GUI正在摆动

5 个答案:

答案 0 :(得分:4)

该行

f1.get();

阻塞,直到你的线程完成执行。如果在UI线程中执行此行,UI当然会冻结。但是你对结果做了什么呢?如果没有更具体的代码,很难为您的问题提供适当的解决方案。

** 编辑 **

您的控制器应该在其自己的线程中执行和处理任务,并且应该与UI完全分离(对于正确的MVC模型)。由于您的代码(args等)中缺少信息,因此可以使用以下示例:

结果

public class WebReaderResult<V> {
   private AtomicReference<V> ref = new AtomicReference<V>();

   public V getResult() { return ref.get(); }
   public void setResult(V value) { ref.set(value); }
}

任务

public abstract class WebTask<V> {
   private WebReaderResult<V> res = new WebReaderResult<V>();

   public WebReaderResult<V> getWebReaderResult() { return res; }

   // handle task here
   public abstract void handle();
}

控制器

public interface WebControllerCallback<V> {
   public void done(V result);
}

public class WebController {
   static private WebController instance;
   static public WebController getInstance() {
      if (null == instance) instance = new WebController();
      return instance;
   }

   private ExecutorService executor = Executors.newSingleThreadExecutor();

   public void handleTask(WebTask<?> t, WebControllerCallback<?> cb) {
      executor.submit(new Runnable() {
         public void run() {
            t.handle();
            cb.done(t.getWebReaderResult());
         }
      });
   }

   // other methods here
}

UI代码

(在您的ActionListener或任何其他UI事件方法中)

WebTask<String> task = new WebTask<String>() {
   public void handle() {
      WebReaderResult<String> result = getWebReaderResult();

      // TODO : handle task and set result here result.getResult();
   }
};
WebControllerCallback<String> callback = new WebControllerCallback<String>() {
   public void done(String result) {
      // TODO : update UI here from result value
   }
};

WebController.getInstance().handleTask(task, callback);

** 编辑2 **

正如其他用户所提到的,自Java 1.6以来,有一个名为SwingWorker的类可以用来做到这一点,但代码要少得多。只需在UI事件中的任何位置放置类似的东西(或创建一个单独的类来实现方法):

new SwingWorker<String, Object>() {
   @Override
   protected String doInBackground() throws Exception {
      // TODO : process result

      return "some result";
   }

   protected void done() {
      // TODO : refresh UI with the value of this.get(); which return a String
   }
}.execute();

最后一次编辑并不像第一个例子那样清晰,但是SwingWorker类确实提供了您要查找的内容。现在,您只需使用doInBackground类委派doneWebReader处理完成的处理(即通过调用wr.run();中的doInBackground,或者其他的东西。)。无论如何,我相信你现在有很多可以继续这样做。

答案 1 :(得分:3)

Swing是单线程的。该线程的名称是事件调度线程(EDT)。当EDT中发生长时间运行的任务时,Swing应用程序似乎会冻结。为了避免这种情况,开发了javax.swing.Timerjavax.swing.SwingUtilitiesjavax.swing.SwingWorker<T,V>等实用程序。这些实用程序类确保修改Swing组件的任何操作都发生在EDT中,并且所有长时间运行的任务都发生在单独的线程(也称为后台线程)中。

我建议您检查一下代码并进一步调查哪些方法阻止一个线程(通常会记录在案)。之后,您可以通过在执行前调用SwingUtilities.isEventDispatchThread()来检查这些方法是否出现在EDT中(这仅用于验证)。

答案 2 :(得分:1)

  我该怎么办?

阅读文档。 :)

说真的:

Future#get()上的JavaDoc说明了这一点(我强调):

  

等待(如有必要)以完成计算,然后检索其结果。

因此,您可以尝试每隔一段时间调用f1.isDone(),直到它返回true,在这种情况下,您可以调用get()

答案 3 :(得分:0)

代码f1.get()是一个阻塞调用。

1)你应该将你的任务细分为粒度单位,然后可能使用多个线程。

2)另外,使用回调机制而不是阻塞,你的任务应该接受一个完成监听器,并通知感兴趣的观察者完成任务,而不是进行阻塞调用。

答案 4 :(得分:0)

直接从官方javadocs(有一些希望有帮助的评论):

final JLabel label;
   class MeaningOfLifeFinder extends SwingWorker<String, Object> {
       @Override
       public String doInBackground() {
           // In here put the time consuming task
           return findTheMeaningOfLife();
       }

       @Override
       protected void done() {
           // In here you'd be in UI thread, so you can actually render some text in a JLabel
           try { 
               label.setText(get());
           } catch (Exception ignore) {
           }
       }
   }

最后这是用法:

   (new MeaningOfLifeFinder()).execute();
相关问题