I'm writing a weather app in javaFx getting data from openweather.org. The whole code for getting JSON from openweather works fine, converting JSON data to an object too. I used lambda expression to implement Runnable
in Platform.runLater();
. The problem is: if I run the Main class, press the button, the app hangs. The thread for importing data works (checked by 2 prints on console) and the main thread "skips" Platform.runLater();
and prints something on the console. I'm not sure what is wrong here.
my Controller class:
package sample;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import java.time.LocalTime;
import java.util.ArrayList;
public class Controller implements Observable{
private static final WeatherStation WEATHER_STATION = new WeatherStation();
protected volatile boolean isRunning = false;
private String response;
private static final int _5MINUTES = 1000*60*5;
private volatile ArrayList<Observer> observers = new ArrayList<>();
@FXML
private TextField cityTextfield;
@FXML
private CategoryAxis xAxis;
@FXML
private NumberAxis yAxis;
@FXML
private Button btn;
@FXML
private LineChart<String, Number> plot;
@FXML
void onclickBtn(ActionEvent event) throws InterruptedException {
WeatherUpdater weatherUpdater = new WeatherUpdater(plot);
setSettings();
WeatherObserver wroclaw = new WeatherObserver();
weatherUpdater.addObserver(wroclaw);
Platform.runLater(()->{
isRunning = true;
while(isRunning) try {
addObserver(wroclaw);
PlotDataUpdater<String, Number> dataUpdater = new PlotDataUpdater<>();
WEATHER_STATION.sendQuery();
response = WEATHER_STATION.getCurrentResponse();
updateObservers();
WeatherConditions weatherConditions = observers.get(0).getWeatherConditions();
LocalTime currentTime = LocalTime.of(LocalTime.now().getHour(), LocalTime.now().getMinute());
dataUpdater.updateSeries(currentTime.toString(), weatherConditions.getMainTemp());
dataUpdater.updatePlot(plot);
Thread.currentThread().sleep(_5MINUTES);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Thread interrupted");
}
});
System.out.println(".................................");
}
@Override
public void addObserver(Observer observer) {
if(!observers.contains(observer)) observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
if(observers.contains(observer)) observers.remove(observer);
}
@Override
public void updateObservers() {
for(Observer o : observers){
o.updateWeatherInfo(response);
}
}
private void setSettings(){
plot.getData().clear();
xAxis.setAutoRanging(true);
yAxis.setAutoRanging(true);
plot.setAnimated(false);
}
}
Classes used for generating plot data, converting things from JSON work as intended. If more code is needed, leave a comment.
@Edit: The code for a class implementing Observable
, Runnable
with own thread:
package sample;
import javafx.scene.chart.XYChart;
import java.time.LocalTime;
import java.util.ArrayList;
public class WeatherUpdater implements Runnable, Observable{
private Thread updater;
private static final WeatherStation WEATHER_STATION = new WeatherStation();
protected volatile boolean isRunning = false;
private String response;
private static final int _5MINUTES = 1000*60*5;
private volatile ArrayList<Observer> observers = new ArrayList<>();
private XYChart<String, Number> plot;
public WeatherUpdater(XYChart<String, Number> plot) {
this.plot = plot;
}
public WeatherUpdater() {
}
public void start(){
updater = new Thread(this,"Weather updater");
updater.start();
}
public void interrupt(){
isRunning = false;
updater.interrupt();
}
public XYChart<String, Number> getPlot() {
return plot;
}
@Override
public void run() {
isRunning = true;
while(isRunning){
try{
PlotDataUpdater<String, Number> dataUpdater = new PlotDataUpdater<>();
WEATHER_STATION.sendQuery();
response = WEATHER_STATION.getCurrentResponse();
updateObservers();
WeatherConditions weatherConditions = observers.get(0).getWeatherConditions();
LocalTime currentTime = LocalTime.of(LocalTime.now().getHour(), LocalTime.now().getMinute());
dataUpdater.updateSeries(currentTime.toString(), weatherConditions.getMainTemp());
dataUpdater.updatePlot(plot);
Thread.sleep(_5MINUTES);
} catch (InterruptedException e) {
updater.interrupt();
System.out.println("Thread interrupted" );
}
}
}
@Override
public void addObserver(Observer observer) {
if(!observers.contains(observer)) observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
if(observers.contains(observer)) observers.remove(observer);
}
@Override
public void updateObservers() {
for(Observer o : observers){
o.updateWeatherInfo(response);
}
}
}
Modification of 'onclickBtn' method combined with the class above:
@FXML
void onclickBtn(ActionEvent event) throws InterruptedException {
WeatherUpdater weatherUpdater = new WeatherUpdater(plot);
setSettings();
WeatherObserver wroclaw = new WeatherObserver();
weatherUpdater.addObserver(wroclaw);
weatherUpdater.start();
System.out.println(".................................");
}
Console output:
.................................
Server status: 200
Exception in thread "Weather updater" java.lang.IllegalStateException: Not on FX application thread; currentThread = Weather updater
at javafx.graphics/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:291)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:424)
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:471)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:206)
at javafx.controls/javafx.scene.chart.LineChart.seriesAdded(LineChart.java:405)
at javafx.controls/javafx.scene.chart.XYChart.lambda$new$1(XYChart.java:160)
at javafx.base/com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at javafx.base/com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.base/javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.base/javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.base/javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.base/javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at javafx.base/javafx.collections.ModifiableObservableListBase.setAll(ModifiableObservableListBase.java:90)
at javafx.base/javafx.collections.ObservableListBase.setAll(ObservableListBase.java:251)
at sample.PlotDataUpdater.updatePlot(PlotDataUpdater.java:36)
at sample.WeatherUpdater.run(WeatherUpdater.java:56)
at java.base/java.lang.Thread.run(Thread.java:834)
I think you misunderstood what Plaftform.runLater is intended for. It's not for running background tasks, in fact it's the complete opposite: it's for delegating tasks to the JavaFX application thread (Or "main thread" in a JavaFX app for simplicity).
Based on your description I would guess that what you want to do is to fetch the data from the web API in a background thread so that it doesn't block the application. To do this you can use JavaFX Task, note that in order to update the GUI afterwards you need to use Plaftform.runLater
来源:https://stackoverflow.com/questions/53656391/multi-thread-app-hangs-after-executing-onclickbtn