多线程应用程序在执行 onclickBtn 后挂起

Multi thread app hangs after executing onclickBtn

我正在用 javaFx 编写一个天气应用程序,从 openweather.org 获取数据。从 openweather 获取 JSON 的整个代码工作正常,也将 JSON 数据转换为对象。我使用 lambda 表达式在 Platform.runLater(); 中实现 Runnable。问题是:如果我 运行 Main class,按下按钮,应用程序挂起。导入数据的线程工作(通过控制台上的 2 个打印检查)和主线程 "skips" Platform.runLater(); 并在控制台上打印一些东西。我不确定这里出了什么问题。

我的控制器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);
    }

}

类 用于生成绘图数据,按预期转换 JSON 的内容。如果需要更多代码,请发表评论。

@Edit:class 实现 ObservableRunnable 的代码,带有自己的线程:

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);
        }
    }
}

修改'onclickBtn'方法结合上面的class:

@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(".................................");
    }

控制台输出:

.................................
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.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(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)

我认为您误解了 Plaftform.runLater 的用途。它不适用于 运行 后台任务,事实上它完全相反:它用于将任务委派给 JavaFX 应用程序线程(或 "main thread" 在 JavaFX 应用程序中为简单起见)。

根据您的描述,我猜您想要做的是在后台线程中从 Web API 获取数据,这样它就不会阻塞应用程序。为此,您可以使用 JavaFX Task,请注意,之后要更新 GUI,您需要使用 Plaftform.runLater