2015-09-28 84 views
1

我正在嘗試調用外部命令並在製作時在TextArea中輸出其輸出。我閱讀了關於JavaFX併發性的文檔,我相信我做了我必須做的工作。正確管理JavaFX中的線程

我使用Task類運行我的工作:

public synchronized void run(ProcessBuilder processBuilder) throws IOException, InterruptedException { 
    ExternalCommandRunner self = this; 
    Thread taskThread = new Thread(new Task<Void>() { 
     @Override 
     public Void call() throws Exception { 
      setActive(); 
      try { 
       runningProcess = processBuilder.start(); 

       StreamPrinter inputStream = new StreamPrinter(runningProcess.getInputStream(), self::handleLog); 
       StreamPrinter errorStream = new StreamPrinter(runningProcess.getErrorStream(), self::handleLog); 
       outputTextArea.clear(); 

       new Thread(inputStream).start(); 
       new Thread(errorStream).start(); 
       runningProcess.waitFor(); 
       return null; 
      } finally { 
       stop.fire(); 
       setInactive(); 
      } 
     } 
    }); 
    taskThread.setDaemon(true); 
    taskThread.start(); 
    taskThread.join(); 
} 

private void handleLog (String line) { Platform.runLater(() -> outputTextArea.appendText(line + "\n")); } 
private void setActive ()   { setState(STOP_ACTIVE_ICON , false)       ; } 
private void setInactive()   { setState(STOP_INACTIVE_ICON, true)        ; } 
private void setState (String iconPath, boolean disableButton) { 
    stop.setGraphic(imageViewFromResource(iconPath, Resources.class)); 
    stop.setDisable(disableButton); 
} 

不過,我發現了以下異常:

Exception in thread "Thread-5" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-5 
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Unknown Source) 
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(Unknown Source) 
    at javafx.scene.Parent$2.onProposedChange(Unknown Source) 
    at com.sun.javafx.collections.VetoableListDecorator.setAll(Unknown Source) 
    at com.sun.javafx.collections.VetoableListDecorator.setAll(Unknown Source) 
    at com.sun.javafx.scene.control.skin.LabeledSkinBase.updateChildren(Unknown Source) 
    at com.sun.javafx.scene.control.skin.LabeledSkinBase.handleControlPropertyChanged(Unknown Source) 
    at com.sun.javafx.scene.control.skin.ButtonSkin.handleControlPropertyChanged(Unknown Source) 
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase.lambda$registerChangeListener$61(Unknown Source) 
    at com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler$1.changed(Unknown Source) 
    at javafx.beans.value.WeakChangeListener.changed(Unknown Source) 
    at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(Unknown Source) 
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(Unknown Source) 
    at javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(Unknown Source) 
    at javafx.beans.property.ObjectPropertyBase.markInvalid(Unknown Source) 
    at javafx.beans.property.ObjectPropertyBase.set(Unknown Source) 
    at javafx.css.StyleableObjectProperty.set(Unknown Source) 
    at javafx.beans.property.ObjectProperty.setValue(Unknown Source) 
    at javafx.scene.control.Labeled.setGraphic(Unknown Source) 
    at com.dici.javafx.components.ExternalCommandRunner.setState(ExternalCommandRunner.java:66) 
    at com.dici.javafx.components.ExternalCommandRunner.setActive(ExternalCommandRunner.java:63) 
    at com.dici.javafx.components.ExternalCommandRunner.access$000(ExternalCommandRunner.java:19) 
    at com.dici.javafx.components.ExternalCommandRunner$1.call(ExternalCommandRunner.java:39) 
    at com.dici.javafx.components.ExternalCommandRunner$1.call(ExternalCommandRunner.java:36) 
    at javafx.concurrent.Task$TaskCallable.call(Unknown Source) 
    at java.util.concurrent.FutureTask.run(Unknown Source) 
    at java.lang.Thread.run(Unknown Source) 

stop簡直是Button。我不正確的是什麼?從文檔,使用Task這種方式應該是足夠的...

+0

那是因爲我阻止用'taskThread.join()'的FX應用程序線程?我需要這個來防止其他線程在命令運行時干擾'TextArea'。我要嘗試另一種同步方法 – Dici

+0

錯誤提示您正在從後臺線程更新UI控件(特別是在標籤上設置圖形)。 FX應用程序線程的所有更新都必須發生。您也不能阻止FX應用程序線程,通過調用'join()',您似乎正在執行該線程。所以我認爲這裏有很多錯誤。也許看到http://stackoverflow.com/questions/30249493/using-threads-to-make-database-requests這可能會有所幫助 - 雖然你的用例看起來有點不同。如果您描述了您正在嘗試實現的內容並創建了[MCVE],則可能會有所幫助。 –

+0

我現在使用'Semaphore'來阻止對該方法的訪問而不阻塞FX主線程,但仍然出現錯誤。根據Task的文檔,使用'new Thread(新任務<...>(){...}).start()'應該可以工作,所以我很困惑。請注意,我現在可以正確更新'TextArea',但不是按鈕中的圖標 – Dici

回答

0

愚蠢的我。我想保證沒有人能夠運行命令(並在文本區域輸出),而另一個命令已經運行,因此​​和Thread.join。但是,這是錯誤的。我也誤解了Task類的文檔。

這裏有兩個錯誤我已經作出,以及如何解決這些問題:

  • Thread.join塊的FX應用程序線程直到任務結束了,所以沒有什麼是永遠顯示在文本區域。我用Semaphore替換了這個同步。信號量阻止第二個線程輸入run方法,並在後臺線程終止時使用Task偵聽器方法釋放。 FX應用程序線程立即退出該方法。

  • Task.call根據文檔,實際上不能與任何JavaFX組件進行交互。只有聽衆方法(cancelled,succeeded,failed)纔可以這樣做。因此,我將撥打setActivesetInactive的電話提取到call方法的外部。

的代碼演示這些變化如下:

private final Semaphore runMutex = new Semaphore(1); 

public void run(ProcessBuilder processBuilder) throws IOException, InterruptedException { 
    runMutex.acquire(); 
    setActive(); 
    ExternalCommandRunner self = this; 
    Thread taskThread = new Thread(new Task<Void>() { 
     @Override 
     public Void call() throws Exception { 
      runningProcess = processBuilder.start(); 

      StreamPrinter inputStream = new StreamPrinter(runningProcess.getInputStream(), self::handleLog); 
      StreamPrinter errorStream = new StreamPrinter(runningProcess.getErrorStream(), self::handleLog); 
      outputTextArea.clear(); 

      new Thread(inputStream).start(); 
      new Thread(errorStream).start(); 
      runningProcess.waitFor(); 
      return null; 
     } 

     @Override protected void cancelled() { super.cancelled(); terminate(); } 
     @Override protected void failed () { super.failed (); terminate(); } 
     @Override protected void succeeded() { super.succeeded(); terminate(); } 

     private void terminate() { 
      stop.fire(); 
      setInactive(); 
      runMutex.release(); 
     } 
    }); 
    taskThread.setDaemon(true); 
    taskThread.start(); 
} 
1

如果我得到它的權利,stacktrace只是說,代碼中有一些其他行在FX應用程序線程之外操縱一些UI控件 - 這一定不會發生。

應用程序代碼的第一線下來的痕跡(不JDK代碼)是: 在com.dici.javafx.components.ExternalCommandRunner.setState(ExternalCommandRunner.java:66)

這似乎是foowloing在你的榜樣行:

stop.setGraphic(imageViewFromResource(iconPath, Resources.class)); 
stop.setDisable(disableButton); 

如果站僅有按鈕和方法setState()操作它,並從您自己的自定義線程中調用,那麼恰好發生了框架抱怨的事情。

嘗試將任何東西操作FX應用程序線程上的UI控件,正如您在handleLog方法中使用Platform.runLater(() -> { ... })一樣。

+0

這確實是問題之一,來自對「任務」文檔的誤解。我沒有使用'Platform.runLater',因爲它預計會用於短期任務,而我提交的則可以持續幾秒鐘。答案在於@James_D評論,如果有人編譯它們來作出答案,我會接受它。否則,我會在一段時間後自己做 – Dici