为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

烙印99 2022-09-21 16:42:51
我和一些朋友一起,试图创建一个回合制游戏。我们在检查用户何时轮到他们时遇到了一些问题,同时保持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();    }}在游戏屏幕.fxml文件的控制器中(不是主屏幕,而是通过登录屏幕和主扩展应用程序加载的屏幕)。public void initialize(URL location, ResourceBundle resources) {    playerButton.setDisable(!GameConnection.yourTurn(user_id, gameTurn));    myStage.setOnCloseRequest(event -> TurnPolling.closedGame = true);}目前,TurnPolling 类只有公共静态布尔封闭游戏,以免将其保留在控制器中。最后一行设置封闭游戏 = true 实际上给了我一个空点器例外,这可能是因为舞台尚未初始化,当我在 initialize() 方法中执行此操作时?我希望仅在轮到玩家时启用玩家按钮,并在游戏屏幕关闭时关闭线程(如果需要)。现在,你必须点击一个按钮来检查它是否轮到你了,每五秒钟再次检查一次,当你关闭游戏时,它不会停止。请告诉我,如果你需要更多的代码或澄清,这是我的第一个大项目,所以我真的不知道放多少在这里。我知道这不是有效的代码,但它是我能做的,而不会让它感觉像是混乱。感谢您的回答!
查看完整描述

1 回答

?
犯罪嫌疑人X

TA贡献2080条经验 获得超4个赞

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


playerButton.setDisable(false);

refreshButton.setDisable(false);

refreshButton.setText("Refresh");

到一个可运行的,它被传递给平台。


Platform.runLater(() -> {

    playerButton.setDisable(false);

    refreshButton.setDisable(false);

    refreshButton.setText("Refresh");

});

请注意,对一个线程中的字段所做的更改在另一个线程中可能不可见,除非声明为 。从 Java 语言规范:TurnPolling.closedGamevolatile


例如,在下面的(损坏的)代码片段中,假定这是一个非字段:this.donevolatileboolean


while (!this.done)

    Thread.sleep(1000);

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


使用任务和服务

JavaFX为所有这些提供了一个更干净的解决方案:任务和服务。


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


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.closedGamevolatilesynchronized


查看完整回答
反对 回复 2022-09-21
  • 1 回答
  • 0 关注
  • 252 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信