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

问题描述 投票:0回答:1

和一些朋友一起,我试图创建一个基于回合的游戏。我们在检查用户轮到他们的时间方面存在一些问题,同时还保持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类只有public static boolean closedGame,以免将其保留在控制器中。设置closedGame = true的最后一行实际上给了我一个NullPointerException,这可能是因为当我在initialize()方法中执行此操作时,Stage尚未初始化?

我希望只在轮到他们时启用玩家按钮,以及在游戏屏幕关闭时关闭线程(如果需要)。现在,你必须点击一个按钮来检查它是否轮到你,每五秒再检查一次,当你关闭游戏时它不会停止。

请告诉我,如果您需要更多代码或澄清,这是我的第一个大项目,所以我真的不知道要放多少钱。我知道这不是正常工作的代码,但它就像我能做到的那样,没有它感觉像是混乱。谢谢你的答案!

java multithreading javafx polling
1个回答
3
投票

首先,重要的是要记住,不允许在JavaFX应用程序线程以外的任何线程中更改JavaFX节点。所以,你的线程需要移动这些行:

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

进入一个传递给Platform.runLater的Runnable:

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的值。

Using Task and Service

JavaFX为所有这些提供了一个更清晰的解决方案:TaskService

服务创建任务。 Service具有可绑定的value属性,该属性始终等于最近创建的Task的值。您可以将按钮属性绑定到Service的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,或者通过在synchronized块(或锁定保护)中包含对它的所有访问。

© www.soinside.com 2019 - 2024. All rights reserved.