我使用基于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正在摆动
答案 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
}
(在您的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
类委派done
和WebReader
处理完成的处理(即通过调用wr.run();
中的doInBackground
,或者其他的东西。)。无论如何,我相信你现在有很多可以继续这样做。
答案 1 :(得分:3)
Swing是单线程的。该线程的名称是事件调度线程(EDT)。当EDT中发生长时间运行的任务时,Swing应用程序似乎会冻结。为了避免这种情况,开发了javax.swing.Timer
,javax.swing.SwingUtilities
和javax.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();