2015-11-12 39 views
1

我正在寫一個Java程序,它使用遺傳算法將一組數字分成兩組,因此它們的總和相等或儘可能接近相等。程序應該在TextArea中追加每次迭代的結果。我不希望程序在計算時凍結,所以我把邏輯放在另一個線程中。更具體地說:JavaFX - 從另一個線程更新TextArea並丟失迭代

  1. 我創建了擴展任務的類Runner。 BestSpecimen是我的類,它用於此處打印迭代次數和此迭代中找到的最佳解決方案列表。它也更新ProgressBas,但並不重要。我通過在我的控制器類中添加以下行來完成它:runnerProgressPB.progressProperty()。bind(runner.progressProperty());
  2. 我想在更新TextArea時做同樣的事情,但是它不能正常工作。重點在於它使用當前迭代更新了TextArea,但刪除了之前迭代的結果。
  3. 我用不同的方法:在我的控制器類,我添加這段代碼: runner.valueProperty().addListener((observable, oldValue, newValue) -> { resultsTA.appendText(newValue + "\n"); });
  4. 簡單,它只是監聽在線程中調用亞軍ValueProperty的變化。在創建在迭代過程中發生的BestSpecimen對象之後,我更新ValueProperty。然而,問題在於 - TextArea中缺少許多迭代。看看截圖。有一百多個迭代缺失! Screenshot
  5. 嗯,我在想什麼?線程運行速度如此之快以至於TextArea無法及時更新信息。我在for循環中加入了一些Thread.sleep(),減少了迭代次數,但程序運行非常緩慢!我使用了Thread.sleep(20),並花了5-10分鐘才完成,但仍有一些遺漏的迭代。這不是解決方案。
  6. 我想達到什麼目的?我想讓跑步者線程在設置ValueProperty後暫停並等待TextArea未更新。我想我應該使用wait()/ notify()構造,但不知道在哪裏。我很新的線程事物...
  7. 這是我的Runner類的call()方法中的主循環。在此之前,只有變量的聲明。

    while (!this.done) { 
         for (int i = 0; i < this.iterations; i++) { 
          current = this.population.get(i); 
          theBestOnes = current.getTheBestSpecimen(); 
          bestCase = new BestSpecimen(i+1, theBestOnes); 
          this.updateValue(bestCase); 
          this.updateProgress(i, iterations); 
          next = Population.createParentPopulationFromExistingOne(current); 
          next.fillPopulation(); 
          this.population.add(this.population.size(), next); 
         } 
         done = true; 
        } 
        return null; 
    
  8. 順便提一下,也有這個討厭的空(見上面的屏幕快照),其被附加到文本區。任何想法如何我可以擺脫它?返回語句在方法結尾處重新提交,我無法返回沒有內容的BestSpecimen對象。

+0

所有'updateXXX(...)'方法都是用來設置屬性的值,而不是提供值的流。因此,他們可能會將多個呼叫合併到一個呼叫中(特別是如果您在兩個相鄰幀渲染之間更新了多次值,可能會發生這種情況)。您需要明確附加到FX應用程序線程上的文本區域。 (請注意,如果您的輸出很大,文本區域可能不是最佳選擇。) –

+0

我該怎麼做?我在另一個線程中運行我的計算,而我無法從這裏完成。我需要在每次迭代中更新我的TextArea,不僅在線程完成時。另外,我使用TextArea justo進行測試,將來會改變它。 – Patryk

回答

0

updateXXX方法Task做他們說什麼:他們更新屬性的值。更新在FX應用程序線程上執行。由於意圖是提供可以在UI中觀察到的值,所以如果在幀渲染之間該值被多次改變,則實際上將僅使用幀的每次渲染中的最新值。這在documentation中明確陳述:

更新value屬性。調用updateValue被合併,並稍後在FX應用程序線程上運行 ,因此,即使從 FX應用程序線程調用updateValue,可能不一定會導致立即更新此屬性的 ,並且中間值可能會合併到 保存事件通知。

最簡單的解決方案如下(雖然它可能不適合下面列出的原因表現良好:

while (!this.done) { 
    for (int i = 0; i < this.iterations; i++) { 
     current = this.population.get(i); 
     theBestOnes = current.getTheBestSpecimen(); 
     bestCase = new BestSpecimen(i+1, theBestOnes); 

     final String text = bestCase.toString()"+\n"; 
     Platform.runLater(() -> resultsTA.appendText(text)); 

     this.updateProgress(i, iterations); 
     next = Population.createParentPopulationFromExistingOne(current); 
     next.fillPopulation(); 
     this.population.add(this.population.size(), next); 
    } 
    done = true; 
} 
return null; 

,顯然擺脫了聽者的valueProperty

這裏的問題是您最終可能會將許多操作安排到FX應用程序線程中,最終可能會花費太多的工作量以至於無法執行重新繪製UI和處理用戶事件等常規任務。

解決這個問題有點棘手。一種選擇是使用BlockingQueue<String>排隊的更新,以及AnimationTimer來更新他們的用戶界面:在你的任務

BlockingQueue<String> textQueue = new LinkedBlockingQueue<>(); 

// ... 

int iterations = ... ; 

現在做

for (int i = 0; i < this.iterations; i++) { 
    current = this.population.get(i); 
    theBestOnes = current.getTheBestSpecimen(); 
    bestCase = new BestSpecimen(i+1, theBestOnes); 

    final String text = bestCase.toString()+"\n"; 
    textQueue.put(text); 

    this.updateProgress(i, iterations); 
    next = Population.createParentPopulationFromExistingOne(current); 
    next.fillPopulation(); 
    this.population.add(this.population.size(), next); 
} 
return null; 

,當你啓動任務,也開始一個AnimationTimer如下:

AnimationTimer timer = new AnimationTimer() { 

    private int updates = 0 ; 
    @Override 
    public void handle(long now) { 
     List<String> newStrings = new ArrayList<>(); 
     updates += textQueue.drainTo(newStrings); 
     StringBuilder sb = new StringBuilder(); 
     newStrings.forEach(sb::append); 
     resultsTA.appendText(sb.toString()); 
     if (updates >= iterations) { 
      stop(); 
     } 
    } 
}; 

timer.play(); 

即使這可能不是表現非常好,因爲可能有大量的文本(和大量的字符串連接來構建它)。您可以考慮使用ListView而不是TextArea,在這種情況下,您可以將隊列直接排到列表視圖的項目中。

+0

一個問題:如果我在我的控制器類中使用resultTA,並且Runner類在單獨的java文件中,我該如何引用它?對於愚蠢的問題抱歉,仍然在學習這些多線程的東西。 – Patryk

+0

假設你正在從你的控制器創建'Runner'類,所以你只需要在你的'Runner'中創建一個對'TextArea'(或其他版本中的'BlockingQueue')的引用,並在創建時將它傳遞給它它。 (如果你想在答案中獲得這種詳細程度,你真的需要寫一個[MCVE],而不是發佈代碼片段。例如,沒有人可以從你的問題中得知你的任務實現是在一個不同的頂級課程。) –

+0

首先,感謝您的建議。我會盡快嘗試您的解決方案併發布結果。另外,這是我第一次在這裏發帖,所以我不知道所有的規則。我會記住,我應該寫出描述問題的簡短代碼示例。如果我在實施解決方案時遇到麻煩,我會這樣做。 – Patryk

相關問題