正确管理JavaFX中的线程

时间:2015-09-28 12:37:43

标签: java multithreading concurrency javafx-8

我正在尝试调用外部命令并在<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <div class="itemshead" style="margin-top:4px;"><div class="items">List of Items <span class="countdown"></span></div></div>生成时将其输出打印出来。我在JavaFX中阅读了关于并发性的文档,我相信我做了我必须做的工作才能使它工作。

我正在使用TextArea课程来完成我的工作:

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这种方式就足够了......

2 个答案:

答案 0 :(得分:1)

如果我做对了,堆栈跟踪只是说代码中还有另一行,其中一些UI控件是从FX应用程序线程外部操纵的 - 这一定不会发生。

应用程序代码的第一行代码(不是J​​DK代码)是:  在File.ReadAllAsync

这似乎是你的例子中的线条:

com.dici.javafx.components.ExternalCommandRunner.setState(ExternalCommandRunner.java:66)

如果stop只是一个按钮,方法setState()操纵它并从你自己的自定义线程调用,那么框架就会抱怨这种情况。

尝试在FX应用程序线程上设置操作UI控件的任何,就像在handleLog方法中使用stop.setGraphic(imageViewFromResource(iconPath, Resources.class)); stop.setDisable(disableButton); 一样。

答案 1 :(得分:0)

愚蠢的我。我想保证在另一个命令已经运行时没有人能够运行命令(并在文本区域打印输出),因此synchronizedThread.join。但是,这是错误的。我也误解了Task类的文档。

以下是我犯过的两个错误以及如何解决这些错误:

  • Thread.join阻止FX应用程序线程,直到任务结束,因此文本区域中不会显示任何内容。我使用Semaphore替换了此同步。信号量阻止第二个线程进入run方法,并在后台线程终止时使用Task侦听器方法释放。 FX应用程序线程立即退出该方法。

  • 根据文档,
  • Task.call实际上不能与任何JavaFX组件交互。只允许侦听器方法(cancelledsucceededfailed)这样做。因此,我将对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();
}