2015-09-28 66 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