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

JavaFX折线图在出现错误后冻结-Bug?,java,javafx,gluon,gluon-mobile,javafxports,Java,Javafx,Gluon,Gluon Mobile,Javafxports,我将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.Arr

我将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)
折线图是这样的

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

我正在使用折线图进行实时记录。下面是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
                    }
编辑:

下面是我出错时的样子。圆点删除的内容越来越多。你看到电话线断了吗

全部代码

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:

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

public class ModbusConnection extends Thread {

    @Override
    public void run() {
        while (start) {
            ...
        }
    }
}
虽然这对于您的后台任务(即modbus连接和通信)来说是非常好的,但您永远不应该在其上执行与UI相关的任务

图表是一个JavaFX节点,当您向其系列中的一个添加新数据点时,如下所示:

public class ModbusConnection extends Thread {

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

非常好!但是time_output.getData.size的结果仅为1。另一件事是,如果我使用Platform.runLater方法,它可以正常工作,但过一段时间,我的三星Android上的FX图表将给出常量值。就像runLater暂停数据一样。因此,我认为我需要进一步修改我的代码,这样baground任务就可以在没有Platform.runLater方法的情况下工作。一个建议!如果PrimaryPresenter.java中有一个线程,SecondaryPresenter中有另一个trhead,它从ModbusConnection.java读取静态字段。Java也有读取modbus的线程。您对此有何看法?只要您添加数据点,序列的大小就应该增加。每次添加数据点时,请添加打印输出以检查其大小。如果大小一直是1,那么你就有问题了。很明显,你有线程问题。正如建议的那样,阅读关于并发性的内容。您应该只有一个背景线程。这应该会为你的图表产生一些结果,但正如我之前所建议的,你应该有一个带有一些ObservableList的模型,它将由线程提供。您的图表将侦听模型中的更改,列出每个图表所属的位置,并将正确更新。现在大小正常。谢谢,我发布了一张图片,显示了我使用Platform.runLater方法时的样子。在我的问题上看编辑2。这会在一段时间后发生。好啊一根线。你是说我不应该创建新的XYChart对象,然后将它们插入到time\u输入和time\u输出中?我应该使用一个可观察的列表来代替?注释不用于扩展讨论或调试会话;这段对话已经结束。请确保问题包含所有要求的相关信息。
public class ModbusConnection extends Thread {

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