Java / JavaFX:轮询数据库以更新单个值,同时保持GUI中的响应能力

时间:2019-04-03 14:02:52

标签: java multithreading javafx polling

我和一些朋友一起尝试创建回合制游戏。我们在检查用户何时回合时有一些问题,同时还要保持GUI的响应速度,并在游戏关闭时关闭我们正在使用的线程。我希望获得一些有关如何执行此操作的信息,但是我不确定问题是与JavaFX相关,与线程相关还是两者都存在。

我已经尝试了尽可能多的搜索,但是即使我认为这很简单,也无法准确找到我想要的东西。现在,当您单击按钮以查看是否轮到我们时,我们有一个线程在运行循环。轮到您时,我们希望禁用一些用户输入,因此,除了保持响应能力之外,我不确定是否确实需要线程。

我也尝试过实现扩展Thread的类,但这似乎只会使问题变得更糟,方法是在每次玩家转弯时都启动一个新线程,或者如果我将循环置于外部,则冻结GUI。线程。

public void refreshButtonPressed(){
    try{
        refreshButton.setDisable(true);
        Thread pollThread = new Thread(() -> {
            System.out.println("Thread started");  //Stop being able to start more threads
            int user_id = 0;
            String gamePin = "xxxxxx";
        while (!GameConnection.yourTurn(user_id, Context.getContext().getGamePin())){ //This method checks the database if it is your turn
            try{
                Thread.sleep(5000);  //So we don't flood the database
            }
            catch (InterruptedException e){
                System.out.println("Interrupted");
                break;
            }
             //If we close the game, stop the thread/while loop. 
            if (TurnPolling.closedGame){
                break;
            }
        }

        playerButton.setDisable(false);
        refreshButton.setDisable(false);
        refreshButton.setText("Refresh");
        System.out.println("Thread ended");
        });
        pollThread.start();
    }catch (Exception e){
        e.printStackTrace();
    }
}

并在gameScreen.fxml文件的控制器中(不是主屏幕,而是通过登录屏幕和主扩展应用程序加载的一个)。

public void initialize(URL location, ResourceBundle resources) {
    playerButton.setDisable(!GameConnection.yourTurn(user_id, gameTurn));
    myStage.setOnCloseRequest(event -> TurnPolling.closedGame = true);
}

现在,TurnPolling类仅具有公共的静态boolean closeGame,以免将其保留在控制器中。设置closedGame = true的最后一行实际上给了我一个NullPointerException,这可能是因为当我在initialize()方法中执行此操作时,舞台尚未初始化吗?

我希望仅在轮到他们时才启用玩家按钮,并希望在gameScreen关闭时关闭线程(如果需要)。现在,您必须单击一个按钮来检查是否轮到您,该按钮每五秒钟检查一次,并且在关闭游戏时不会停止。

请告诉我是否需要更多代码或说明,这是我的第一个大项目,所以我真的不知道在这里放多少。我知道这不是有效的代码,但是在不感到混乱的情况下,我可以做很多事情。谢谢您的回答!

1 个答案:

答案 0 :(得分:3)

首先,请务必记住,除JavaFX应用程序线程外,不允许在任何其他线程中更改JavaFX节点。因此,您的线程需要移动这些行:

playerButton.setDisable(false);
refreshButton.setDisable(false);
refreshButton.setText("Refresh");

进入一个Runnable并传递给Platform.runLater

Platform.runLater(() -> {
    playerButton.setDisable(false);
    refreshButton.setDisable(false);
    refreshButton.setText("Refresh");
});

请注意,除非声明为TurnPolling.closedGame,否则一个线程中对volatile字段的更改可能不会在另一个线程中可见。来自the Java Language Specification

  

例如,在下面的(破碎)代码片段中,假设this.done是非{volatile boolean字段:

while (!this.done)
    Thread.sleep(1000);
     

编译器可以自由读取字段this.done一次,并在每次循环执行中重用缓存的值。这意味着即使另一个线程更改了this.done的值,循环也永远不会终止。

使用任务和服务

JavaFX为以下所有方面提供了更清洁的解决方案:TaskService

服务创建任务。服务具有可绑定的value属性,该属性始终等于最近创建的Task的值。您可以将按钮属性绑定到服务的value属性:

int user_id = 0;

Service<Boolean> turnPollService = new Service<Boolean>() {
    @Override
    protected Task<Boolean> createTask() {
        return new Task<Boolean>() {
            @Override
            protected Boolean call()
            throws InterruptedException {

                updateValue(true);

                String gamePin = Context.getContext().getGamePin();

                while (!GameConnection.yourTurn(user_id, gamePin)) {
                    Thread.sleep(5000);

                    if (TurnPolling.closedGame){
                        break;
                    }
                }

                return false;
            }
        };
    }
};

playerButton.disableProperty().bind(turnPollService.valueProperty());
refreshButton.disableProperty().bind(turnPollService.valueProperty());

refreshButton.textProperty().bind(
    Bindings.when(
        turnPollService.valueProperty().isEqualTo(true))
        .then("Waiting for your turn\u2026")
        .otherwise("Refresh"));

玩家回合结束后,您将拨打turnPollService.restart();

无论您使用服务还是仅使用Platform.runLater,您都仍需要使TurnPolling.closedGame成为线程安全,方法是使其成为volatile或将对它的所有访问都包含在{{ 1}}块(或锁卫)。