JavaFX折线图出现错误后冻结 - Bug?

问题描述 投票:0回答:1

我正在使用JavaFX和Gluon Mobile。非常好的创建移动应用程序的框架。但是我有一个JavaFX折线图,它在出错后会冻结。

Exception in thread "JavaFX Application Thread"
java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    at java.util.ArrayList$Itr.next(ArrayList.java:859)
    at java.util.Collections$UnmodifiableCollection$1.next(Collections.java:1042)
    at javafx.scene.chart.LineChart.layoutPlotChildren(LineChart.java:468)
    at javafx.scene.chart.XYChart.layoutChartChildren(XYChart.java:731)
    at javafx.scene.chart.Chart$1.layoutChildren(Chart.java:94)
    at javafx.scene.Parent.layout(Parent.java:1087)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Scene.doLayoutPass(Scene.java:552)
    at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2397)
    at com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:398)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:397)
    at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:424)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:518)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:498)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:491)
    at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:319)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
    at com.sun.glass.ui.gtk.GtkApplication.lambda$null$5(GtkApplication.java:139)
    at java.lang.Thread.run(Thread.java:748)

折线图看起来像这样。

enter image description here

但过了一会儿,我可以看到点后点慢慢消失,然后就没有了。就像Java FX折线图停止更新一样。我已经发布了一些评论,其中包含了什么,以及实际发生了什么。

enter image description here

我正在使用折线图进行实时记录。这是Java代码。

                    /*
                     * Get the time format in HH:mm:ss
                     */
                    LocalDateTime now = LocalDateTime.now();
                    String time = dtf.format(now); 

                    if (countMeasurements < MEASUREMENTS) {
                        time_output.getData().add(new XYChart.Data<String, Number>(time, output));
                        time_input.getData().add(new XYChart.Data<String, Number>(time, input));
                        countMeasurements++;
                    } else {
                        /*
                         * Now insert
                         */
                        time_output.getData().add(new XYChart.Data<String, Number>(time, output)); // <-- No update after a while
                        time_input.getData().add(new XYChart.Data<String, Number>(time, input)); // <-- No update after a while
                        /*
                         * Delete the first object
                         */
                        time_output.getData().remove(0); // <--- This works
                        time_input.getData().remove(0); // <-- This works
                    }

编辑:

这是我收到错误时的样子。点删除越来越多。你看到这条线已经消失了吗?

enter image description here

整个代码

package com.gluonapplication.thread;


import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import com.gluonhq.charm.glisten.mvc.View;
import de.re.easymodbus.modbusclient.ModbusClient;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;

public class ModbusConnection extends Thread{

    /*
     * Static
     */
    private static boolean running;
    private static boolean start;

    /*
     * Fields from Scene Builder
     */
    private TextField statusTextField;
    private TextField ipAddressTextField;
    private ComboBox<String> startSignalComboBox;
    private TextField predictHorizonTextField;
    private TextField controlHorizonTextField;
    private TextField sampleTimeTextField;
    private TextField referencePointTextField;
    private TextField portTextField;
    private TextField slopeTextField;
    private TextField offsetTextField;
    private LineChart<String, Number> lineChart_primary;
    private Series<String, Number> time_output;
    private LineChart<String, Number> lineChart_third;
    private Series<String, Number> time_input;

    /*
     * Modbus
     */
    private ModbusClient modbusClient;
    private int MEASUREMENTS = 20;
    DateTimeFormatter dtf;
    int[] writeRegisters = new int[11];
    private int countMeasurements;

    @SuppressWarnings("unchecked")
    public ModbusConnection(View primaryView, View secondaryView, View thirdView) {
        /*
         * We start this thread as default
         */
        start = true; 

        /*
         * Initial modbus start
         */
        writeRegisters[6] = 0; 

        /*
         * For secondaryView
         */
        statusTextField = (TextField) secondaryView.lookup("#statusTextField");
        ipAddressTextField = (TextField) secondaryView.lookup("#ipAddressTextField");
        startSignalComboBox = (ComboBox<String>) secondaryView.lookup("#startSignalComboBox");
        predictHorizonTextField = (TextField) secondaryView.lookup("#predictHorizonTextField");
        controlHorizonTextField = (TextField) secondaryView.lookup("#controlHorizonTextField");
        sampleTimeTextField = (TextField) secondaryView.lookup("#sampleTimeTextField");
        referencePointTextField = (TextField) secondaryView.lookup("#referencePointTextField");
        portTextField = (TextField) secondaryView.lookup("#portTextField");
        slopeTextField = (TextField) secondaryView.lookup("#slopeTextField");
        offsetTextField = (TextField) secondaryView.lookup("#offsetTextField");

        /*
         * For primaryView
         */
        lineChart_primary = (LineChart<String, Number>) primaryView.lookup("#lineChart");

        /*
         * Declare the data object inside the chart
         */
        time_output = new Series<String, Number>();
        time_output.setName("Output");
        lineChart_primary.getData().add(time_output);

        /*
         * For thirdView
         */
        lineChart_third = (LineChart<String, Number>) thirdView.lookup("#lineChart");

        /*
         * Declare the data object inside the chart
         */
        time_input = new Series<String, Number>();
        time_input.setName("Input");
        lineChart_third.getData().add(time_input);


        /*
         * This will prevent so we don't get NullPointerException
         */
        modbusClient = null;

        /*
         * For time
         */
        dtf = DateTimeFormatter.ofPattern("HH:mm:ss");  

        /*
         * Reset 
         */
        countMeasurements = 0;
    }

    @Override
    public void run() {
        while (start) {
            while (running == true || writeRegisters[6] == 1) {

                /*
                 * Connect to Modbus server 
                 */
                if(modbusClient == null) {
                    modbusClient = new ModbusClient(ipAddressTextField.getText(),Integer.parseInt(portTextField.getText()));
                    try {
                        modbusClient.Connect();
                    } catch (Exception e) {
                        statusTextField.setText("Cannot connect");
                    }
                }else if(modbusClient.isConnected() == false){
                    modbusClient = new ModbusClient(ipAddressTextField.getText(),Integer.parseInt(portTextField.getText()));
                    try {
                        modbusClient.Connect();
                    } catch (Exception e) {
                        statusTextField.setText("Cannot connect");
                    }
                }

                /*
                 * Write registers at address 0
                 */
                try {                   

                    /*
                     * What start signal should we use. 
                     */
                    int mode = startSignalComboBox.getSelectionModel().getSelectedIndex();
                    switch (mode) {
                        case 0:
                            writeRegisters[0] = 255; // PWM 100%
                            break;
                        case 1:
                            writeRegisters[0] = 230; // PWM 90%
                            break;
                        case 2:
                            writeRegisters[0] = 204; // PWM 80%
                            break;
                        case 3:
                            writeRegisters[0] = 179; // PWM 70%
                            break;
                        case 4:
                            writeRegisters[0] = 153; // PWM 60%
                            break;
                        case 5:
                            writeRegisters[0] = 128; // PWM 50%
                            break;
                        case 6:
                            writeRegisters[0] = 102; // PWM 40%
                            break;
                        case 7:
                            writeRegisters[0] = 77; // PWM 30%
                            break;
                        case 8:
                            writeRegisters[0] = 51; // PWM 20%
                            break;
                        case 9:
                            writeRegisters[0] = 26; // PWM 10%
                            break;
                        default:
                            writeRegisters[0] = 255; // PWM 100%
                            break;
                    }

                    /*
                     * Get the prediction horizon as int
                     */
                    writeRegisters[1] = Integer.parseInt(predictHorizonTextField.getText());

                    /*
                     * Get the control horizon as int
                     */
                    writeRegisters[2] = Integer.parseInt(controlHorizonTextField.getText());

                    /*
                     * Get the sample time in as int
                     */
                    writeRegisters[3] = Integer.parseInt(sampleTimeTextField.getText()); 

                    /*
                     * Get the reference point in two ints
                     */
                    writeRegisters[4] = (int) Float.parseFloat(referencePointTextField.getText());  
                    writeRegisters[5] = (int) ((Float.parseFloat(referencePointTextField.getText()) - ((float) writeRegisters[4])) * 10000); 

                    /*
                     * Get if the system is running
                     */
                    writeRegisters[6] = running ? 1 : 0;

                    /*
                     * Get the slope
                     */
                    writeRegisters[7] = (int) Float.parseFloat(slopeTextField.getText());
                    writeRegisters[8] = (int) ((Float.parseFloat(slopeTextField.getText()) - ((float) writeRegisters[7])) * 10000); 

                    /*
                     * Get the offset
                     */
                    writeRegisters[9] = (int) Float.parseFloat(offsetTextField.getText());  
                    writeRegisters[10] = (int) ((Float.parseFloat(offsetTextField.getText()) - ((float) writeRegisters[9])) * 10000); 

                    /*
                     * Write 11 elements from address 0
                     */
                    modbusClient.WriteMultipleRegisters(0, writeRegisters);

                } catch (Exception e) {
                    statusTextField.setText("Cannot write");
                } 


                /*
                 * Read 3 registers at the beginning from address 12
                 */
                try {
                    int[] registersRead = modbusClient.ReadHoldingRegisters(12, 3); // two first are output (float) and the last is the input (int)

                    /*
                     * Get the output value 
                     */

                    System.out.println("registersRead[0] = " + registersRead[0] + " registersRead[1] = " + registersRead[1] );
                    double output = ((double) registersRead[0]) + ((double) registersRead[1]) / 10000.0;
                    System.out.println("Output : " + output);

                    /*
                     * Get the input value
                     */
                    int input = registersRead[2];

                    /*
                     * Get the time format in HH:mm:ss
                     */
                    LocalDateTime now = LocalDateTime.now();
                    String time = dtf.format(now); 
                    System.out.println("Time : " + time);

                    if (countMeasurements < MEASUREMENTS) {
                        time_output.getData().add(new XYChart.Data<String, Number>(time, output));
                        time_input.getData().add(new XYChart.Data<String, Number>(time, input));
                        countMeasurements++;
                    } else {
                        /*
                         * Now insert
                         */
                        System.out.println("Add time_output");
                        time_output.getData().add(new XYChart.Data<String, Number>(time, output));
                        System.out.println("Add time_input");
                        time_input.getData().add(new XYChart.Data<String, Number>(time, input));
                        /*
                         * Delete the first object
                         */
                        System.out.println("Delete");
                        time_output.getData().remove(0); 
                        time_input.getData().remove(0);
                    }

                } catch (Exception e) {
                    statusTextField.setText("Cannot read");
                } 


                try {
                    Thread.sleep((long) (1000 * Double.parseDouble(sampleTimeTextField.getText())));
                } catch (Exception e) {
                    statusTextField.setText("Cannot delay");
                }

                statusTextField.setText("Running");

            }

            /*
             * This is because we don't want update so fast
             */
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                statusTextField.setText("Cannot delay");
            }

            /*
             * Disconnect if we have been using modbusClient object before
             */
            if(modbusClient != null) {
                try {
                    if(modbusClient.isConnected()) {
                        modbusClient.Disconnect();
                        statusTextField.setText("Stopped");
                    }
                } catch (Exception e) {
                    statusTextField.setText("Cannot disconnect");
                }
            }
        }
    }

    public static boolean isRunning() {
        return running;
    }

    public static void setRunning(boolean running) {
        ModbusConnection.running = running;
    }

    public static boolean isStart() {
        return start;
    }

    public static void setStart(boolean start) {
        ModbusConnection.start = start;
    }

}

编辑2:enter image description here

java javafx gluon gluon-mobile javafxports
1个回答
1
投票

如评论中所述,您正在创建后台任务:

public class ModbusConnection extends Thread {

    @Override
    public void run() {
        while (start) {
            ...
        }
    }
}

虽然这对你的后台任务来说非常好,即你的modbus连接和通信,你永远不应该在它上面做UI相关的任务。

Chart是一个JavaFX节点,当你向它的一个系列中添加一个新的数据点时,如下所示:

public class ModbusConnection extends Thread {

    @Override
    public void run() {
        while (start) {
            ...
            time_output.getData().add(new XYChart.Data<String, Number>(time, output));
            ...               
        }
    }
}

触发布局传递以呈现相关节点,但这应该仅在UI线程(JavaFX应用程序线程)中完成。

因此,作为初始修复,修改您的代码:

public class ModbusConnection extends Thread {

    @Override
    public void run() {
        while (start) {
            ...
            Platform.runLater(() -> 
                time_output.getData().add(new XYChart.Data<String, Number>(time, output)));
            ...               
        }
    }
}

请注意,正如上面评论中提到的@Slaw,Platform::runLater的JavaDoc说:

public static void runLater​(Runnable runnable)

将来在某个未指定的时间在JavaFX应用程序线程上运行指定的Runnable。可以从任何线程调用的此方法将Runnable发布到事件队列,然后立即返回给调用者。

所以这看起来正是我们在这种情况下所需要的。

但是如果你继续阅读:

注意:应用程序应避免使用过多的待处理Runnable充斥JavaFX。否则,应用程序可能会无响应。鼓励应用程序将多个操作批量处理为更少的runLater调用。此外,应尽可能在后台线程上执行长时间运行操作,从而释放JavaFX应用程序线程以进行GUI操作。

因此,在第二步中,您应该尝试批处理后台任务中的所有调用,例如:

public class ModbusConnection extends Thread {

    @Override
    public void run() {
        while (start) {
            ...
            Platform.runLater(() -> {
                time_output.getData().add(new XYChart.Data<String, Number>(time, output));
                time_input.getData().add(new XYChart.Data<String, Number>(time, input));
                if (time_output.getData().size() > MEASUREMENTS) {
                    time_output.getData().remove(0); 
                    time_input.getData().remove(0);
                }
            });
            ...               
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.