[英]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.