使用 ScheduledExecutorService 更新 JavaFX 元素
Use ScheduledExecutorService to update JavaFX elements
目前我正在制作一个程序,提醒我什么时候给我的植物浇水,同时也会考虑天气。我想显示当前的温度和湿度,我已经编写了足够好的代码。但是,此代码仅在通过按下按钮手动 运行 方法时有效,并且当我尝试在 ScheduledExecutorService 中 运行 时抛出 Exception in thread "pool-3-thread-1" java.lang.IllegalStateException: Not on FX application thread; currentThread = pool-3-thread-1
。根据我的理解,JavaFX 不允许其他线程在没有 Platform.runLater 的情况下编辑 JavaFX 组件,但是我似乎找不到任何关于 Platform.runLater 与 ScheduledExecutorService.
结合的信息
这是我的更新方法:
public void update() {
final Runnable updater = new Runnable() {
public void run() {
humidityLabel.setText("Humidity: " + Double.toString(Weather.getHumidity()) + "%");
humidityDialArm.setRotate(Weather.getHumidity() * 1.8);
tempLabel.setText("Temperature: " + Double.toString(Weather.getTemperature()) + "°F");
temperatureDialArm.setRotate(Weather.getTemperature()*1.5);
icon = Weather.getIcon();
conditionLabel.setText(Weather.getCondition());
}
};
final ScheduledFuture<?> updaterHandle = scheduler.scheduleAtFixedRate(updater, 10, 10, TimeUnit.MINUTES);
}
这是我的主要方法:
public static void main(String[] args) {
App app = new App();
launch();
app.update();
}
我在 GitHub 上发现了一个类似的问题 here, however I haven't been able to find a way to get Platform.runLater to work well with the ScheduledExecutorService. I also found this,但是除了可以修复之外,我不知道该问题的解决方法是什么。我还尝试在 main 中放置一个 while 循环,它会不断更新它,但这只会导致程序挂起并最终崩溃。即使它确实有效,这也会使它长时间 运行 无法使用,因为我正在使用的 API 限制了每天的 GET 请求数量。
使用ScheduledService
javafx.concurrent.ScheduledService
class 提供了一种重复执行操作并轻松与 FX 线程通信的方法。这是一个例子:
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.scene.image.Image;
public class WeatherService extends ScheduledService<WeatherService.Result> {
@Override protected Task<Result> createTask() {
return new Task<>() {
@Override protected Result call() throws Exception {
// this is invoked on the background thread
return new Result(
Weather.getTemperature(),
Weather.getHumidity(),
Weather.getCondition(),
Weather.getIcon()
);
}
};
}
public record Result(double temperature, double humidity, String condition, Image icon) {}
}
然后在使用该服务的地方添加一个 on-succeeded 处理程序来处理更新 UI:
service.setOnSucceeded(e -> {
var result = service.getValue();
// update UI (this handler is invoked on the UI thread)
});
要让它每 10 分钟执行一次,初始延迟 10 分钟,以匹配您对 ScheduledExecutorService
所做的操作,您可以这样做:
service.setDelay(javafx.util.Duration.minutes(10));
service.setPeriod(javafx.util.Duration.minutes(10));
// you can define the thread pool used with 'service.setExecutor(theExecutor)'
首次配置服务时。您还需要维护对服务的强引用;如果它被垃圾收集,则不会重新安排任务。
使用Platform#runLater(Runnable)
如果您出于某种原因必须使用 ScheduledExecutorService
,那么您应该 运行 在 runLater
调用中更新 UI 的代码。这是一个例子:
public void update() {
final Runnable updater =
() -> {
// get information on background thread
double humidity = Weather.getHumidity();
double temperature = Weather.getTemperature();
Image icon = Weather.getIcon();
String condition = Weather.getCondition();
// update UI on FX thread
Platform.runLater(
() -> {
humidityLabel.setText("Humidity: " + humidity + "%");
humidityDialArm.setRotate(humidity * 1.8);
tempLabel.setText("Temperature: " + temperature + "°F");
temperatureDialArm.setRotate(temperature * 1.5);
iconView.setImage(icon);
conditionLabel.setText(condition);
});
};
scheduler.scheduleAtFixedRate(updater, 10, 10, TimeUnit.MINUTES);
}
目前我正在制作一个程序,提醒我什么时候给我的植物浇水,同时也会考虑天气。我想显示当前的温度和湿度,我已经编写了足够好的代码。但是,此代码仅在通过按下按钮手动 运行 方法时有效,并且当我尝试在 ScheduledExecutorService 中 运行 时抛出 Exception in thread "pool-3-thread-1" java.lang.IllegalStateException: Not on FX application thread; currentThread = pool-3-thread-1
。根据我的理解,JavaFX 不允许其他线程在没有 Platform.runLater 的情况下编辑 JavaFX 组件,但是我似乎找不到任何关于 Platform.runLater 与 ScheduledExecutorService.
这是我的更新方法:
public void update() {
final Runnable updater = new Runnable() {
public void run() {
humidityLabel.setText("Humidity: " + Double.toString(Weather.getHumidity()) + "%");
humidityDialArm.setRotate(Weather.getHumidity() * 1.8);
tempLabel.setText("Temperature: " + Double.toString(Weather.getTemperature()) + "°F");
temperatureDialArm.setRotate(Weather.getTemperature()*1.5);
icon = Weather.getIcon();
conditionLabel.setText(Weather.getCondition());
}
};
final ScheduledFuture<?> updaterHandle = scheduler.scheduleAtFixedRate(updater, 10, 10, TimeUnit.MINUTES);
}
这是我的主要方法:
public static void main(String[] args) {
App app = new App();
launch();
app.update();
}
我在 GitHub 上发现了一个类似的问题 here, however I haven't been able to find a way to get Platform.runLater to work well with the ScheduledExecutorService. I also found this,但是除了可以修复之外,我不知道该问题的解决方法是什么。我还尝试在 main 中放置一个 while 循环,它会不断更新它,但这只会导致程序挂起并最终崩溃。即使它确实有效,这也会使它长时间 运行 无法使用,因为我正在使用的 API 限制了每天的 GET 请求数量。
使用ScheduledService
javafx.concurrent.ScheduledService
class 提供了一种重复执行操作并轻松与 FX 线程通信的方法。这是一个例子:
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.scene.image.Image;
public class WeatherService extends ScheduledService<WeatherService.Result> {
@Override protected Task<Result> createTask() {
return new Task<>() {
@Override protected Result call() throws Exception {
// this is invoked on the background thread
return new Result(
Weather.getTemperature(),
Weather.getHumidity(),
Weather.getCondition(),
Weather.getIcon()
);
}
};
}
public record Result(double temperature, double humidity, String condition, Image icon) {}
}
然后在使用该服务的地方添加一个 on-succeeded 处理程序来处理更新 UI:
service.setOnSucceeded(e -> {
var result = service.getValue();
// update UI (this handler is invoked on the UI thread)
});
要让它每 10 分钟执行一次,初始延迟 10 分钟,以匹配您对 ScheduledExecutorService
所做的操作,您可以这样做:
service.setDelay(javafx.util.Duration.minutes(10));
service.setPeriod(javafx.util.Duration.minutes(10));
// you can define the thread pool used with 'service.setExecutor(theExecutor)'
首次配置服务时。您还需要维护对服务的强引用;如果它被垃圾收集,则不会重新安排任务。
使用Platform#runLater(Runnable)
如果您出于某种原因必须使用 ScheduledExecutorService
,那么您应该 运行 在 runLater
调用中更新 UI 的代码。这是一个例子:
public void update() {
final Runnable updater =
() -> {
// get information on background thread
double humidity = Weather.getHumidity();
double temperature = Weather.getTemperature();
Image icon = Weather.getIcon();
String condition = Weather.getCondition();
// update UI on FX thread
Platform.runLater(
() -> {
humidityLabel.setText("Humidity: " + humidity + "%");
humidityDialArm.setRotate(humidity * 1.8);
tempLabel.setText("Temperature: " + temperature + "°F");
temperatureDialArm.setRotate(temperature * 1.5);
iconView.setImage(icon);
conditionLabel.setText(condition);
});
};
scheduler.scheduleAtFixedRate(updater, 10, 10, TimeUnit.MINUTES);
}