“java.lang.IllegalStateException:任务只能从 FX 应用程序线程使用”,尽管任务是从 FX 应用程序线程创建和运行的

Wil*_*ers -2 java concurrency user-interface animation javafx

我有一个单一场景的JavaFX应用程序,它运行一个动画,动画需要在两种状态之间来回切换。
基于“Java 如何编程,11/e,早期对象”中的示例 ,我编写了一个控制器,该控制器在初始化方法中创建类似的设置,
以及一个具有布尔值的任务,该任务用布尔值向动画计时器发出何时通过翻转切换状态的信号其值然后睡眠。无论
我做什么,我都会不断收到从该方法抛出的“java.lang.IllegalStateException:任务只能从 FX 应用程序线程使用” 。call()

这是控制器的简化版本:

public class AnimationController {
    @FXML public AnimationTimer myAnimationTimerExtention;
    private ExecutorService executorService;

   public void initialize() {
        myAnimationTimerExtention.setState(false);
        TimeingTask task = new TimeingTask (Duration.ofSeconds(5));

        task .valueProperty().addListener((observableValue, oldValue, newValue) -> {
            myAnimationTimerExtention.setState(false);
        });

        executorService = Executors.newFixedThreadPool(1);
        executorService.execute(timeTrafficLightTask);
        executorService.shutdown();
    }
Run Code Online (Sandbox Code Playgroud)

这是我的任务:

public class TimeingTask  extends Task<Boolean> {
    private final Duration duration;

    public TimeingTask(Duration duration) {
        this.duration = duration;
        updateValue(true);
    }

    @Override
    protected Boolean call() throws Exception {
        while (!isCancelled()) {
            updateValue(!getValue());
            try {
                Thread.sleep(duration);
            } catch (InterruptedException e) {
                updateMessage("timing task got interrupted");
            }

        }

        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

到目前为止我已经尝试过:

  1. 将初始化移动到按钮的操作中完成
  2. 删除监听器并更新值
  3. Thread.currentThread().getName()正在控制器中的应用程序板上进行验证。
  4. 检查任务是否以某种方式被取消,以查看问题是否与此javafx bug有关,它没有被取消。
  5. 编译并运行我基于此的示例,看看这是否与我的 IDE 配置或 JDK 版本有关,该示例有效。
  6. 围绕updateValue()在 a 中Platform.runLater(),例外被抛出在Theard.sleep()

在上述所有场景中都会引发异常

Jam*_*s_D 6

抛出异常是因为您只能value从 FX 应用程序线程直接访问该属性。您可以从任何线程调用updateValue(),但不能getValue()从后台线程调用。如果仔细检查堆栈跟踪,您应该看到类似以下内容:

java.lang.IllegalStateException: Task must only be used from the FX Application Thread
  at javafx.concurrent.Task.checkThread()
  at javafx.concurrent.Task.getValue()
  at yourpackage.TimeingTask.call()
Run Code Online (Sandbox Code Playgroud)

意味着异常是由方法中的调用getValue()(而不是调用updateValue())引发的call()

Task.getValue()实现是

@Override public final V getValue() { checkThread(); return value.get(); }
Run Code Online (Sandbox Code Playgroud)

private void checkThread() {
    if (started && !isFxApplicationThread()) {
        throw new IllegalStateException("Task must only be used from the FX Application Thread");
    }
}
Run Code Online (Sandbox Code Playgroud)

事实上,文档明确指出

因为它Task是为与 JavaFX GUI 应用程序一起使用而设计的,所以它确保对其公共属性的每次更改以及状态、错误和事件处理程序的更改通知都发生在主 JavaFX 应用程序线程上。从后台线程(包括call()方法)访问这些属性将导致引发运行时异常。

(我的强调。)

getValue()因此,当您从call()方法(当然是在后台线程上运行)调用时,它会抛出IllegalStateException.


无论如何,线程对于您在这里尝试做的事情来说是非常过分的。如此处所述,您可以使用以下方法大大简化此过程Timeline

public void initialize() {
    myAnimationTimerExtention.setState(false);
    KeyFrame frame = new KeyFrame(Duration.ofSeconds(5), e -> 
        myAnimationTimerExtention.setState(! myAnimationTimerExtention.getState()));
    Timeline timeline = new Timeline(frame);
    timeline.setCycleCount(Animation.INDEFINITE);
    timeline.play();

}
Run Code Online (Sandbox Code Playgroud)

或者直接将功能构建到您的 中AnimationTimer,这样可以轻松检查传递给方法的时间戳handle()并在需要时翻转状态。

这消除了对子Task类 的需要,Executor并且意味着应用程序不会通过创建额外的线程来消耗额外的资源。


以防万一问题中的代码是简化的,并且您确实需要出于某种未知原因使用后台线程,只需隐藏该值即可。强调一下:除非需要,否则不要增加多线程的所有复杂性。对于您所描述的功能来说,上面的解决方案是一个更好的解决方案。

public class TimeTrafficLightTask  extends Task<Boolean> {
    private final Duration duration;
    private boolean state = true ;

    public TimeTrafficLightTask(Duration duration) {
        this.duration = duration;
        updateValue(state);
    }

    @Override
    protected Boolean call() throws Exception {
        while (!isCancelled()) {
            state = !state ;
            updateValue(state);
            try {
                Thread.sleep(duration);
            } catch (InterruptedException e) {
                updateMessage("timing task got interrupted");
            }

        }

        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @WildLAppers,当你有这样的奇怪要求时,你应该在问题中说明它们。这会节省大量时间并减少混乱。 (3认同)