【问题标题】:Is the operation of toggling a button in JavaFX atomic?在 JavaFX 中切换按钮的操作是原子的吗?
【发布时间】:2014-11-27 04:38:39
【问题描述】:

JavaFX 操作中的切换按钮将由 2 个单独的线程访问。

1. 一旦用户单击(切换按钮状态),就会调用一个线程,并将 a) 在操作系统中做某事 b) 检查 (a) 是否成功 c) 成功退出/退出并在失败时将切换按钮返回到之前的状态

2 另一个线程将监视与先前操作异步的事件,如果发生特定事件,它将更改按钮状态。

在锁定按钮状态方面,我是否需要在线程 12 之间提供同步?

编辑:James_D 提出的想法似乎是合理的,但我只是想提出一个替代方案(但其有效性仍有待证明)。 如何使用同步代码块,并使用锁定特定按钮的引用,例如:

// getting the reference to the button 
@FXML
private ToggleButton tButtonToBeSynchronized

// Thread1
synchronized(tButtonToBeSynchronized) {
// do stuff with button upon user click 
}

// Thread2
synchronized(tButtonToBeSynchronized) {
// poll system every X seconds
// when asynchronous event occurs (not related to UI events)
// update tButtonToBeSynchronized state
}

如果它们被不同的控制器类调用,这会起作用吗? (假设对 tButtonToBeSynchronized 的引用是通过引用传递的——而不是 FXML 框架的值传递?

【问题讨论】:

  • 不,这行不通。您还需要 FX 库代码在按钮上进行同步。它没有 - 它假定您只从 FX 应用程序线程访问属于场景图一部分的节点。即使它确实“工作”了,在 Java 8 中,如果您尝试从另一个线程操作状态,也会在运行时抛出异常的显式检查。

标签: multithreading javafx synchronization togglebutton


【解决方案1】:

与大多数 UI 工具包一样,JavaFX 采用单线程模型。您应该只从 FX 应用程序线程访问属于场景图一部分的节点的状态。因此,切换按钮不是原子操作,并且您描述的代码不能保证像您当前设置的那样工作。在 Java 8 中,它可能会抛出 RuntimeException

JavaFX 提供了启用与后台线程互操作性的功能。其中最低级别是Platform.runLater(Runnable r),它在FX 应用程序线程上执行r。因此,您的监控线程(问题中的第 2 项)应该使用

更改切换按钮的状态
Platform.runLater( () -> toggleButton.setSelected(...) );

还有一个javafx.concurrent API。这提供了一个Task 类,除其他外,它充当Runnablejava.util.concurrent.FutureTask,另外还有一组回调方法,用于提交要在FX 应用程序线程上在@ 中的各个点执行的代码987654332@ 的生命周期。

因此,您应该将问题中的第 1 项实现为:

ExecutorService exec = ... ; // e.g. Executors.newCachedThreadPool();

toggleButton.selectedItemProperty().addListener((obs, wasSelected, isNowSelected) -> {
    if (isNowSelected) {
        Task<Void> task = new Task<Void>() {
            @Override
            public Void call() throws Exception {
                // do something on OS
                // throw exception if failed
                return null ;
            }
        };
        task.setOnFailed(event -> toggleButton.setSelected(wasSelected));
        exec.submit(task);
    }
});

如果你更喜欢返回一个表示成功或失败的值,你可以这样做

Task<Boolean> task = new Task<Boolean>() {
    @Override
    public Boolean call() {
        // do work...
        boolean successful =  ... ;
        return successful ;
    }
};
task.setOnSucceeded( event -> {
    boolean wasSuccessful = task.getValue();
    // ...
});

【讨论】:

  • 感谢您抽出宝贵时间回复。我假设这会将事件处理/执行/调度的控制转移到内部 JavaFX 事件队列结构中? Qt 框架也是这样吗?
  • 我不知道 Qt,但是是的; Platform.runLater(...) 在 FX 应用程序线程上执行提供的 RunnableonSucceededonFailedTask 的类似处理程序及其updateXXX(...) 方法在同一线程上执行。因此,在这些方法和处理程序中读取和写入属于场景图一部分的节点的状态是安全的。
猜你喜欢
  • 2017-07-14
  • 2015-01-10
  • 2018-09-23
  • 1970-01-01
  • 1970-01-01
  • 2016-11-05
  • 2014-12-16
  • 2017-06-14
  • 1970-01-01
相关资源
最近更新 更多