所以我有这样的事情:
public class test {
final ProgressBar bar = new ProgressBar(0.0);
final int result;
final worker work = new worker();
// GUI Scene change happens here
new Thread(() -> {
result = work.doSomething(this.bar);
}).start();
}
public class worker{
public int doSomething(ProgressBar bar){
for(int i = 1; i <= 1000000; i++)
Platform.runLater(() -> { bar.setProgress(i/100000.0); });
return 51;
}
}
一切正常,直到我不得不等待其他事情才能完成,然后才能继续运行,所以我改变了它:
public class test {
final ProgressBar bar = new ProgressBar(0.0);
TextArea area = new TextArea();
int result;
final worker work = new worker();
final worker work2 = new worker2();
final CountDownLatch latch1 = new CountDownLatch(1);
// GUI Scene change happens here
new Thread(() -> {
result = work.doSomething(this.bar);
latch1.CountDown();
}).start();
latch1.await();
new Thread(() -> {
work2.doSomething(result, area);
}).start();
}
public class worker{
public int doSomething(ProgressBar bar){
for(int i = 1; i <= 1000000; i++)
Platform.runLater(() -> { bar.setProgress(i/100000.0); });
return 51;
}
}
public class worker2{
ArrayList<String> list = new ArrayList<>();
public int doSomething(int index, TextArea area){
Platform.runLater(() -> { area.append(list.get(index)); });
}
}
稍后我会做这样的事情:
public class test {
final ProgressBar bar = new ProgressBar(0.0);
TextArea area = new TextArea();
int result;
final worker work = new worker();
final worker2 work2 = new worker2();
final worker3 work3 = new worker3();
final CountDownLatch latch1 = new CountDownLatch(1);
final CountDownLatch latch2 = new CountDownLatch(Map.keySet().size());
// This already has values
// it is not really a file array list in the map, but it is easier to show it this way
Map<String, ArrayList<File>> mapTypes;
// GUI Scene change happens here
new Thread(() -> {
result = work.doSomething(this.bar);
latch1.CountDown();
}).start();
latch1.await();
new Thread(() -> {
work2.doSomething(result, area);
}).start();
// Even thought I don't use it here I need a for each on the keyset
mapTypes.keySet().forEach((String s) -> {
new Thread(() -> {
// Here I actually load classes with a reflection
work3.doSomething(mapTypes.get(s), area);
latch2.CountDown();
}).start();
}
latch2.await();
System.out.println("Done");
}
public class worker{
public int doSomething(ProgressBar bar){
for(int i = 1; i <= 1000000; i++)
Platform.runLater(() -> { bar.setProgress(i/100000.0); });
return 51;
}
}
public class worker2{
ArrayList<String> list = new ArrayList<>();
public int doSomething(int index, TextArea area){
Platform.runLater(() -> { area.append(list.get(index)); });
}
}
public class worker3{
public int doSomething(Arraylist<File> files, TextArea area){
for (File f : files)
Platform.runLater(() -> { area.append(f.getName()); });
}
}
现在我的gui在切换场景时开始滞后 - 意思是 - 整个thread1处理自己并且然后 gui加载了所有东西。经过研究后我认为这是因为主线程正在处理runLater“请求”,并且由于 await()主线程必须等到第一个辅助线程进入倒计时()
我的问题是,如何管理主线程在第一个后台线程完成之前没有启动第二个后台线程?奖金问题:更新我的GUI而不是 Plattform.runlater()的更有效方法是什么?
注意:
我也查看了this Question,但它没有完全解决我的问题,因为我不需要排队线程。我更需要知道如何使主线程等到子线程完成然后继续。但是,主线程不能完全不活动,而是管理传入的更新请求。
提前谢谢
使用的技术: - NetBeans
- JavaFX(无FXML - 代码中设计的所有内容)
- CSS
- Java(显然)
- Windows 10专业版
答案 0 :(得分:3)
您的代码的(主要)问题是您在JavaFX应用程序线程上调用latch.await()
,这是一种阻塞方法。由于JavaFX应用程序线程负责更新UI,因此可以防止在latch.await()
版本发布之前重新绘制UI。
您的问题的基本前提是错误的:您从不想要使UI线程暂停,因为它总是会使UI无响应并阻止任何更新。相反,你应该考虑&#34;执行一个工作单元&#34;在后台,可能会在UI进行时对其进行更新,然后在完成后台工作时做一些事情。
您的代码的另一个潜在问题是您通过Runnable
向FX应用程序主题提交了大量Platform.runLater()
个问题。您可能需要对这些进行限制,以便他们不会淹没&#34; FX应用程序线程。
您可以使用Task
API解决所有这些问题。 Task
类是Runnable
的实现,其call()
方法是从run()
方法调用的。它有各种updateXXX
方法,包括updateProgress()
,它们更新FX应用程序线程上的各种属性,并限制这些调用,以便不再安排FX应用程序线程可以处理的调度。最后,它具有回调方法,例如setOnSucceeded()
,当后台工作完成时(或者,通常,当任务改变其生命周期状态时)在FX应用程序线程上调用。
(注意:我重命名了你的类,使它们符合推荐的命名约定。像大多数Java开发人员一样,我发现阅读那些不符合这些规范的代码非常困难。)
public class Test {
final ProgressBar bar = new ProgressBar(0.0);
TextArea area = new TextArea();
int result;
final Worker work = new Worker();
final Worker2 work2 = new Worker2();
// GUI Scene change happens here
work.setOnSucceeded(e -> work2.doSomething(work.getValue(), area));
bar.progressProperty().bind(work.progressProperty());
new Thread(work).start();
}
public class Worker extends Task<Integer> {
@Override
protected Integer call(){
for(int i = 1; i <= 1000000; i++)
updateProgress(i, 1000000);
return 51;
}
}
public class Worker2{
ArrayList<String> list = new ArrayList<>();
// this is now executed on the FX Application Thread: there is no need
// for Platform.runLater():
public int doSomething(int index, TextArea area){
area.append(list.get(index));
}
}
你的第二个例子有点复杂,而且我根本不确定你是否需要额外的线程:你Worker3
似乎唯一要做的就是在文本区域添加一行,无论如何必须在FX应用程序线程上完成。但是,如果您的真实应用程序需要为每个文件进行后台工作,这就是它的样子。我建议使用任务池,而不是手动创建这么多任务。这看起来像是:
public class Test {
final ProgressBar bar = new ProgressBar(0.0);
TextArea area = new TextArea();
int result;
final Worker work = new Worker();
final Worker2 work2 = new Worker2();
final ExecutorService exec = Executors.newCachedThreadPool();
// This already has values
// it is not really a file array list in the map, but it is easier to show it this way
Map<String, ArrayList<File>> mapTypes;
// GUI Scene change happens here
work.setOnSucceeded(e -> {
work2.doSomething(work.getValue(), area);
Task<Void> processAllFiles = new Task<Void>() {
@Override
protected Void call() throws Exception {
final CountDownLatch latch2 = new CountDownLatch(Map.keySet().size());
mapTypes.keySet().forEach((String s) -> {
exec.submit(() -> {
work3.doSomething(mapTypes.get(s), area);
latch2.CountDown();
});
});
latch2.await();
return null ;
}
};
processAllFiles.setOnSucceeded(evt -> {
// executed on fx application thread:
System.out.println("Done");
});
});
bar.progressProperty().bind(work.progressProperty());
exec.submit(work);
}