javax.swing.SwingWorker 中的问题
-
06-07-2019 - |
题
我已经制作了一个 Swings 应用程序,但有一个问题如下:
我从事件调度线程启动了一个名为“Thread-Main”的 SwingWorker 线程,并将 GUI 的 JLabel 引用传递给“Thread-Main”。
现在我已经从“Thread-Main”启动了 10 个线程。
现在我希望所有 10 个线程都应该更新 JLabel。
我怎样才能做到这一点?
有人告诉我,我可以通过首先将 SwingWorker 的所有 10 个线程子类化,然后调用publish("") 方法并在该“publish”方法中传递字符串,然后通过以下方法收集所有已发布的字符串来做到这一点: “主线程”
@Override
protected void process(List<String> labelStrings) {
String count = labelStrings.get(labelStrings.size() - 1);
label.setText(count); // label is a reference to a label in the GUI
}
- 上述做法是否正确?
- 这 10 个线程应该是 SwingWorker 的子类吗?
- 还有其他方法可以做到这一点吗?
解决方案
- 不 - 这是错误的做法 如果你想控制线程数 你自己。为什么?因为如果你看
SwingWorker
代码你会看到它使用了ThreadPoolExecutor
内部最多包含 10 个线程。如果您启动了多个SwingWorker
同时它们都将使用这个执行器运行。然而, 您无法直接控制后台线程是否并行执行. - 参见第 1 点。
我推荐的解决方案是:
- 创建一个单
SwingWorker
. - 内
doInBackground()
方法直接启动 10 个线程或使用ExecutorService
. - 用一个
CountDownLatch
或者CompletionService
在主线程之间同步(即SwingWorker
后台线程)和工作线程。
- 创建一个单
例子
定义要调用的工作线程数并声明要更新的 JLabel。
final int nThreads = 10;
JLabel myLbl = new JLabel();
将我们希望执行的工作单元定义为 Callable<String>
. 。这 String
结果将用于更新 JLabel
.
private static class MyCallable implements Callable<String> {
public String call() { ... }
}
现在开始一个 SwingWorker
, ,这将依次启动多个并行工作线程来执行任何处理。工作人员不会通过调用返回结果 done()
(因此 Void
类型)但是 将要 马歇尔中间 String
通过调用将结果返回到 Swing 线程 process(String... chunks)
.
new SwingWorker<Void, String>() {
// See method definitions below.
}.execute();
定义 doInBackground()
使用以下命令启动工作线程并阻止每个结果 CompletionService
.
public Void doInBackground() throws Exception {
// Define executor service containing exactly nThreads threads.
ExecutorService execService = Executors.newFixedThreadPool(nThreads);
// Define completion service that will contain the processing results.
CompletionService compService = new ExecutorCompletionService(execService);
// Submit work to thread pool using the CompletionService. Future<String>
// instances will be added to the completion service's internal queue until complete.
for (int i=0; i<nThreads; ++i) {
compService.submit(new MyCallable());
}
// Take results from each worker as they appear and publish back to Swing thread.
String result;
while ((result = compService.take().get()) != null) {
publish(result);
}
}
现在我们实施 process(String... chunks)
简单地更新 JLabel
当被叫时。
public void process(String... chunks) {
if (chunks.length > 0) {
// Update label with last value in chunks in case multiple results arrive together.
myLbl.setText(chunks[chunks.length - 1]);
}
}
最后我们重写 done()
将任何异常编组回 Swing 线程。
public void done() {
try {
get(); // Will return null (as per Void type) but will also propagate exceptions.
} catch(Exception ex) {
JOptionPane.show ... // Show error in dialog.
}
}
其他提示
也许更简单的方法是在SwingUtilities.invokeLater(...)方法中包装更新GUI的代码。
编辑:在您想要更新标签的个别线程中:
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
label.setText(...);
}
});
为什么你需要10个线程?为什么不会有一个额外的线程呢?
真正的问题:你要解决的问题是什么?
回答你的直接问题:
1)是的,这是正确的方法 2)是的,线程应该是SwingWorkers(如果你使用netbeans,你也可以使用Tasks,它们也是SwingWorker的子类)
3)如果你想从edt中获得一个单独的线程;然后你需要使用swingworker;所以方式就是这样做的。
祝你好运!