Ошибка производительности при отображении точек JFreeChart при работе в EDT - PullRequest
1 голос
/ 06 февраля 2020

Справочная информация:

Хорошо известное требование Swing о наилучшей практике состоит в том, что код, взаимодействующий со структурой Swing, также должен выполняться в EDT (Thread Dispatch Thread).

Таким образом, я изменил свой код, чтобы мои обновления на основе JFreeChart работали в EDT. Тем не менее, полная задача отображения диаграммы, которая обычно занимает около 7 минут до окончания sh в «нормальном» потоке, становится задачей нескольких часов при работе в EDT!


Что я делать неправильно? Я неправильно понял урок Swing Concurrency? Мне действительно нужно запустить org.jfree.data.time.TimeSeries.addOrUpdate(date, double) внутри EDT?

Пожалуйста, сообщите!


Подробности:

Нажав кнопку Swing GUI, моя программа запускает трудоемкую задачу. По сути, он читает (большой) файл с парными значениями (date, double), а затем показывает их, используя инфраструктуру JFreeChart.

Поскольку это трудоемкая задача при чтении и отображении данных, JProgreessBar показывает пользователю статус прогресса на переднем плане, а график обновляется в фоновом режиме (пользователь по-прежнему может визуально видеть каждое обновление графика за индикатором выполнения).

Это работало нормально, пока я не решил просмотрите код, чтобы мои данные диаграммы обновлялись и отображались в Swing EDT. Затем завершенное задание, которое обычно занимало около 7 минут до окончания sh, начинало занимать несколько часов до завершения!


Вот список потоков, которые я использую:

1) Поскольку задача запускается прослушивателем Swing Button, она выполняется в режиме EDT. JProgressBar также работает в этом же потоке;

2) При отображении JProgressBar второй («нормальный») поток создается и выполняется в фоновом режиме. Вот где проделана тяжелая работа.

Включает обновление статуса JProgressBar в другом потоке (путем вызова JProgressBar.setvalue()) и обновление моего JFreeChart графика (путем вызова * 1049). *, который автоматически обновляет org.jfree.chart.ChartPanel).

Обновление графика во втором («нормальном») потоке обычно занимало около 7 минут до окончания sh. Без каких-либо заметных проблем.


Однако, зная, что большинство методов объекта Swing не являются "потоко-безопасными", а ChartPanel является просто компонентом Swing GUI для отображения объекта JFreeChart, я решил чтобы запустить код обновления моего графика TimeSeries.addOrUpdate(date, double) внутри EDT.

Все еще работающий во втором «нормальном» потоке, я протестировал следующий асинхронный код:

javax.swing.SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        TimeSeries.addOrUpdate(date, double);
    }
});

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

Затем я попробовал следующий синхронный код:

try {
    javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
        public void run() {
            TimeSeries.addOrUpdate(date, double);
        }
    });
} catch (InvocationTargetException | InterruptedException e) {
    e.printStackTrace();
}   

И именно здесь я обнаружил проблему с производительностью : теперь завершенное задание, которое раньше занимало около 7 минут до конца sh, теперь начинало занимать несколько часов!


Итак, мой вопрос:

Что я делаю не так? Я неправильно понял урок Swing Concurrency? Действительно ли мне нужно запускать TimeSeries.addOrUpdate (date, double) внутри EDT?

Пожалуйста, сообщите!


UPDATE:
Полный код был бы слишком велик для показа здесь, но вы можете найти снимок кода ниже.
Возможно, единственное, что заметно в коде, это то, что я использую Reflection. Это потому, что я использую обобщенный класс c ProgressBar, который вызывает в фоновом режиме любой класс, который я отправляю в качестве аргумента (хотя это не ясно показано на снимке ниже).

//BUTTON LISTENER (EDT)
public void actionPerformed(ActionEvent arg0) {
    new Process_offline_data();
}


public Process_offline_data() {
    //GET DATA
    String[][] data = get_data_from_file();

    //CREATE PROGRESS BAR
    int maximum_progressBar = data.length;
    JProgressBar jpb = init_jpb(maximum_progressBar);

    //JDIALOG MODAL WINDOW
    JDialog jdialog = create_jdialog_window(jpb);


    Object class_to_invoke_obj = (Object) new Show_data_on_chart();
    String main_method_str = "do_heavy_staff"; 

    Runnable r = new Runnable() {
        public void run() {             

            //REFLECTION
            Method method = null;
            try {
                method = class_to_invoke_obj.getClass().getDeclaredMethod(main_method_str, JProgressBar.class, String[][].class);
            } catch (NoSuchMethodException | SecurityException e1) {
                e1.printStackTrace();
                jdialog.dispose();      //UNBLOCKS MAIN THREAD
                return;
            }

            try {
                method.invoke(class_to_invoke_obj, jpb, data);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
                e1.printStackTrace();
                jdialog.dispose();      //UNBLOCKS MAIN THREAD
                return;
            }
            //----------------


            jdialog.dispose();      //UNBLOCKS MAIN THREAD
        }
    };
    new Thread(r).start();
    //----------------


    //THIS IS STILL EDT
    jdialog.setVisible(true);       //BLOCKS HERE UNTIL THE THREAD CALLS jdialog.dispose();
}



public class Show_data_on_chart {
    public void do_heavy_staff(JProgressBar jpb, String[][] data) {

        TimeSeries time_series = get_TimeSeries();      //JFreeChart datamodel

        int len = data.length;
        for (int i=0; i<len; i++) {
            jpb.setValue(i+1);

            Millisecond x_axys_millisecond = convert_str2date(data[i][0]);
            Double y_axys_double = convert_str2double(data[i][1]);

            try {
                javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        //AUTOMATICALLY UPDATES org.jfree.chart.ChartPanel
                        time_series.addOrUpdate(x_axys_millisecond, y_axys_double);
                   }
                });
            } catch (InvocationTargetException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

1 Ответ

1 голос
/ 08 февраля 2020

Вот так я решил проблему обновления графика.

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.data.xy.XYSeries;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;
import org.jfree.chart.plot.XYPlot;
import java.lang.reflect.InvocationTargetException;
import javax.swing.SwingUtilities;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;

public class App extends ApplicationFrame {

    XYSeries sin = new XYSeries("Sin");

    public App(String applicationTitle, String chartTitle) {
        super(applicationTitle);
        JFreeChart xylineChart = ChartFactory.createXYLineChart(chartTitle, "X", "Y",                                                                                                 new XYSeriesCollection(sin),
            PlotOrientation.VERTICAL, false, true, false);

    ChartPanel chartPanel = new ChartPanel(xylineChart);
    chartPanel.setPreferredSize(new java.awt.Dimension(560, 367));
    final XYPlot plot = xylineChart.getXYPlot();

    XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(true, false);
    plot.setRenderer(renderer);
    setContentPane(chartPanel);
}

public Runnable r = new Runnable() {
    double x, y;
    int i;

    public void run() {
        int steps = 69999;
        for (i = 0; i < steps; i++) {
            //sample plot data
            x = Math.PI * 2.0 * 10.0 / ((double) steps) * ((double) i);
            y = Math.sin(x);

            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        if ((i % 1000) == 0) {
                            //adding data and redrawing chart
                            sin.addOrUpdate(x, y);
                        } else {
                            //adding point without redrawing of the chart
                            sin.add(x, y, false);
                        }
                    }
                });
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //redrawing chart if all data loaded
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    sin.fireSeriesChanged();
                }
            });
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
};

public Runnable rupdate = new Runnable() {
    public void run() {
        while (true) {
            //redrawing chart
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        sin.fireSeriesChanged();
                    }
                });
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //waiting for next update
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
};

public static void main(String[] args) {
    final App chart [] = new App[1];

    try {
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                chart[0] = new App(null, null);
                chart[0].pack();
                RefineryUtilities.centerFrameOnScreen(chart[0]);
                chart[0].setVisible(true);
            }
        });
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    Thread job = new Thread(chart[0].r);
    job.start();
    Thread job2 = new Thread(chart[0].rupdate);
    job2.start();
}
}

Приведенный выше код включает в себя два решения. Вы можете использовать любой из них или оба. Диаграмма может обновляться во время подачи данных. Например, каждые 100-е очко и после последнего poit. В конце концов вы можете создать внешний поток, который обновляет график через некоторое время. Я использовал updateAndWait каждый раз вместо updateLater.

В вашем коде не используйте подобные отражения. Вы должны сделать интерфейс. Например:

public interface IHardWork {
    public void do_heavy_staff(JProgressBar jpb, String[][] data);
}

и реализовать его на каждом объекте, который выполняет работу:

public class Show_data_on_chart implements IHardWork {
    public void do_heavy_staff(JProgressBar jpb, String[][] data) {
        // TODO Auto-generated method stub
    }
}

, а затем использовать его:

IHardWork hwObj = new Show_data_on_chart();
hwObj.do_heavy_staff(jpb, data);
hwObj = new OtherHWObj();
hwObj.do_heavy_staff(jpb, data);

В конце концов Вы можете сделать Базовый класс для него и использовать полиморфизм.

...