Multi thread app hangs after executing onclickBtn

霸气de小男生 提交于 2019-12-02 22:37:42

问题


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)

回答1:


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!