Java/JavaFX:轮询数据库以获取单个值更新,同时在 GUI 中保持响应能力
Java/JavaFX: Poll a database for single value update, while keeping responsiveness in GUI
我和一些朋友一起尝试制作一款回合制游戏。我们在检查用户何时轮到他们、同时保持 GUI 响应以及在游戏关闭时关闭我们现在使用的线程方面存在一些问题。我希望获得有关如何执行此操作的一些信息,但我不确定问题是与 JavaFX 相关、与线程相关还是与两者有关。
我已尽我所能进行搜索,但就是找不到我要找的东西,尽管我认为这很简单。现在,当您单击按钮检查是否轮到您时,我们有一个线程 运行 循环。当轮到你的时候,我们希望禁用一些用户输入,所以我不确定我们是否真的需要一个线程,除了保持响应能力。
我也尝试过实施 class 扩展线程,但这似乎只会让问题变得更糟,因为要么在每次轮到玩家时启动一个新线程,要么冻结 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 class 只有 public static boolean closedGame,以免将其保留在控制器中。最后一行设置 closedGame = true 实际上给了我一个 NullPointerException,这可能是因为 Stage 还没有初始化,当我在 initialize() 方法中这样做时?
我希望仅在轮到玩家时启用玩家按钮,并在 gameScreen 关闭时关闭线程(如果需要)。现在,你必须点击一个按钮来检查是否轮到你,每五秒再次检查一次,并且不会在你关闭游戏时停止。`
如果您需要更多代码或说明,请告诉我,这是我的第一个大项目,所以我真的不知道该放多少。我知道这不是工作代码,但它是我能做的尽可能多的,而不会让它感觉混乱。感谢您的任何回答!
首先,重要的是要记住,不允许在 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");
});
请注意,除非已声明 volatile
,否则在一个线程中对 TurnPolling.closedGame
字段所做的更改可能在另一个线程中不可见。来自 the Java Language Specification:
For example, in the following (broken) code fragment, assume that this.done
is a non-volatile
boolean
field:
while (!this.done)
Thread.sleep(1000);
The compiler is free to read the field this.done
just once, and reuse the cached value in each execution of the loop. This would mean that the loop would never terminate, even if another thread changed the value of this.done
.
使用任务和服务
JavaFX 为这一切提供了更简洁的解决方案:Task and Service.
服务创建任务。 Service 有一个可绑定值 属性,它始终等于最近创建的 Task 的值。您可以将按钮属性绑定到服务的值 属性:
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
块(或锁守卫)中。
我和一些朋友一起尝试制作一款回合制游戏。我们在检查用户何时轮到他们、同时保持 GUI 响应以及在游戏关闭时关闭我们现在使用的线程方面存在一些问题。我希望获得有关如何执行此操作的一些信息,但我不确定问题是与 JavaFX 相关、与线程相关还是与两者有关。
我已尽我所能进行搜索,但就是找不到我要找的东西,尽管我认为这很简单。现在,当您单击按钮检查是否轮到您时,我们有一个线程 运行 循环。当轮到你的时候,我们希望禁用一些用户输入,所以我不确定我们是否真的需要一个线程,除了保持响应能力。
我也尝试过实施 class 扩展线程,但这似乎只会让问题变得更糟,因为要么在每次轮到玩家时启动一个新线程,要么冻结 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 class 只有 public static boolean closedGame,以免将其保留在控制器中。最后一行设置 closedGame = true 实际上给了我一个 NullPointerException,这可能是因为 Stage 还没有初始化,当我在 initialize() 方法中这样做时?
我希望仅在轮到玩家时启用玩家按钮,并在 gameScreen 关闭时关闭线程(如果需要)。现在,你必须点击一个按钮来检查是否轮到你,每五秒再次检查一次,并且不会在你关闭游戏时停止。`
如果您需要更多代码或说明,请告诉我,这是我的第一个大项目,所以我真的不知道该放多少。我知道这不是工作代码,但它是我能做的尽可能多的,而不会让它感觉混乱。感谢您的任何回答!
首先,重要的是要记住,不允许在 JavaFX 应用程序线程以外的任何线程中更改 JavaFX 节点。因此,您的线程需要移动这些行:
playerButton.setDisable(false);
refreshButton.setDisable(false);
refreshButton.setText("Refresh");
进入传递给 Platform.runLater:
的 RunnablePlatform.runLater(() -> {
playerButton.setDisable(false);
refreshButton.setDisable(false);
refreshButton.setText("Refresh");
});
请注意,除非已声明 volatile
,否则在一个线程中对 TurnPolling.closedGame
字段所做的更改可能在另一个线程中不可见。来自 the Java Language Specification:
For example, in the following (broken) code fragment, assume that
this.done
is a non-volatile
boolean
field:while (!this.done) Thread.sleep(1000);
The compiler is free to read the field
this.done
just once, and reuse the cached value in each execution of the loop. This would mean that the loop would never terminate, even if another thread changed the value ofthis.done
.
使用任务和服务
JavaFX 为这一切提供了更简洁的解决方案:Task and Service.
服务创建任务。 Service 有一个可绑定值 属性,它始终等于最近创建的 Task 的值。您可以将按钮属性绑定到服务的值 属性:
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
块(或锁守卫)中。