我和一些朋友一起尝试创建回合制游戏。我们在检查用户何时回合时有一些问题,同时还要保持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关闭时关闭线程(如果需要)。现在,您必须单击一个按钮来检查是否轮到您,该按钮每五秒钟检查一次,并且在关闭游戏时不会停止。
请告诉我是否需要更多代码或说明,这是我的第一个大项目,所以我真的不知道在这里放多少。我知道这不是有效的代码,但是在不感到混乱的情况下,我可以做很多事情。谢谢您的回答!
答案 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为以下所有方面提供了更清洁的解决方案:Task和Service。
服务创建任务。服务具有可绑定的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}}块(或锁卫)。