Линейный график JavaFX зависает после ошибки - Ошибка? - PullRequest
0 голосов
/ 20 марта 2019

Я использую 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: введите описание изображения здесь

1 Ответ

1 голос
/ 20 марта 2019

Как поясняется в комментариях, вы создаете фоновое задание:

public class ModbusConnection extends Thread {

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

, и хотя это отлично подходит для вашей задачи background , то есть для подключения и связи по протоколу Modbus, вы никогда не должны выполнять на нем задачи, связанные с пользовательским интерфейсом.

A Chart - это узел JavaFX, и когда вы добавляете новую точку данных к одной из ее серий, как это:

public class ModbusConnection extends Thread {

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

, который запускает этап макета для визуализации связанного узла, но это должно быть сделано только в потоке пользовательского интерфейса (поток приложения 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 упоминается в комментариях выше, JavaDoc для Platform :: runLater говорит, что:

public static void runLater​(Runnable runnable)

Запустите указанный Runnable в потоке приложений JavaFX в неустановленное время в будущем. Этот метод, который может быть вызван из любого потока, отправит Runnable в очередь событий, а затем сразу же вернется к вызывающей стороне.

Так что это выглядит именно то, что нам нужно в этом случае.

Но если вы продолжите читать:

ПРИМЕЧАНИЕ: приложения должны избегать переполнения JavaFX слишком большим количеством ожидающих Runnables. В противном случае приложение может перестать отвечать на запросы. Приложениям рекомендуется объединять несколько операций в меньшее количество вызовов runLater. Кроме того, по возможности следует выполнять длительные операции в фоновом потоке, освобождая поток приложений 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));
                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);
                }
            });
            ...               
        }
    }
}
...