[英]JavaFX make threads wait and threadsave gui update
所以我有这样的事情:
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在切换场景时开始滞后-意思是-整个线程1自行处理, 然后 gui加载了所有内容。 经过研究后,我认为这是因为主线程处理了runLater的“请求”,并且由于await() ,主线程不得不等到第一个辅助线程到达CountDown() 。
我的问题是,如何管理主线程在第一个后台线程完成之前没有启动第二个后台线程? 额外的问题:什么是更新我的GUI而不是Plattform.runlater()的更有效方法?
注意:
我也看过这个问题,但是由于我不需要排队线程,因此它不能完全解决我的问题。 我还需要知道如何使主线程等到子线程完成后才继续执行。 但是,主线程不得完全处于非活动状态,而应管理传入的更新请求。
先感谢您
使用的技术:-NetBeans
-JavaFX(无FXML-代码中设计的所有内容)
-CSS
-Java(显然)
-Windows 10专业版
代码的(主要)问题是您正在JavaFX Application Thread上调用latch.await()
,这是一种阻塞方法。 由于JavaFX Application Thread负责更新UI,因此可以防止在latch.await()
释放之前重新绘制UI。
这个问题的基本前提是错误的:您永远都不想使UI线程暂停,因为它总是使UI无响应并阻止任何更新。 相反,您应该在后台考虑“执行工作单元”,可能会在UI进行时对UI进行更新,然后根据后台工作的完成进行一些操作。
代码的另一个潜在问题是,您正在通过Platform.runLater()
向FX应用程序线程提交大量Runnable
。 您可能需要限制它们,以免它们“淹没” FX应用程序线程。
您可以使用Task
API解决所有这些问题。 该Task
类是一个实现Runnable
,其call()
方法是从调用run()
方法。 它具有各种updateXXX
方法,包括updateProgress()
,它们可以更新FX Application线程上的各种属性,并限制这些调用,以便调度的时间不会超过FX Application线程可以处理的范围。 最后,它具有回调方法,例如setOnSucceeded()
,在后台工作完成时(或通常在任务更改其生命周期状态时)在FX Application Thread上调用。
(注意:我重命名了您的类,使它们符合建议的命名约定。像大多数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 Application上完成。无论如何还是线程。 但是,如果您的实际应用程序需要每个文件的后台工作,则外观将是这样。 我建议为此使用任务池,而不要手动创建这么多任务。 这看起来像:
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);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.