java - 在 EDT 中运行时显示 JFreeChart 点的性能问题

标签 java multithreading swing jfreechart edt

背景:

A well-known Swing best-practice requirement is that code that interacts with the Swing framework must also execute in EDT (Event Dispatch Thread).

因此,我更改了代码,以使基于 JFreeChart 的更新在 EDT 中运行。 然而,在“正常”线程上通常需要大约 7 分钟才能完成的完整图表显示任务,在 EDT 中运行时会变成几个小时的任务!


我做错了什么?我是否误解了 Swing 并发类(class)?我真的必须运行org.jfree.data.time.TimeSeries.addOrUpdate(date, double)在 EDT 内?

请指教!


详细信息:

单击 Swing GUI 按钮,我的程序会触发一项耗时的任务。 基本上,它读取一个包含对值(日期、 double )的(大)文件,然后使用 JFreeChart 框架显示它们。

因为这是一项耗时的任务,在读取和显示数据时,JProgreessBar在前台向用户显示进度状态,而图表在后台更新(用户仍然能够在进度条后面直观地看到每个图表更新)。

这工作得很好,直到我决定检查代码以更新我的图表数据并将其显示在 Swing EDT 中。然后,原本需要 7 分钟左右完成的任务,开始需要几个小时才能完成!


这是我正在使用的线程列表:

1) 由于该任务是由 Swing Button Listener 触发的,因此它在 EDT 中运行。 JProgressBar也在同一个线程中运行;

2)同时显示JProgressBar ,第二个(“正常”)线程被创建并在后台执行。这是完成繁重工作的地方。

它包括 JProgressBar 的更新另一个线程上的状态(通过调用 JProgressBar.setvalue() )以及我的 JFreeChart 的更新图表(通过调用 TimeSeries.addOrUpdate(date, double) ,它会自动更新 org.jfree.chart.ChartPanel )。

在我的第二个(“正常”)线程中更新图表通常需要大约 7 分钟才能完成。没有任何明显的问题。


然而,知道大多数 Swing 对象方法都不是“线程安全的”并且 ChartPanel只是一个用于显示 JFreeChart 的 Swing GUI 组件对象,我决定运行我的图表更新代码 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 分钟才能完成的完整任务,现在开始需要几个小时才能完成!


所以,我的问题是:

我做错了什么?我是否误解了 Swing 并发类(class)?我真的必须在 EDT 内运行 TimeSeries.addOrUpdate(date, double) 吗?

请指教!


更新:
完整的代码太大,无法在此处显示,但您可以在下面找到代码快照。
也许,代码中唯一值得注意的是我使用了反射。这是因为我使用了一个通用的 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();
            }
        }
    }
}

最佳答案

这就是我解决更新图表问题的方法。

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 个点以及最后一个点之后。最终,您可以创建外部线程,在一段时间后更新图表。我每次都使用 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);

最终你可以为它创建一个基类并使用多态性。

关于java - 在 EDT 中运行时显示 JFreeChart 点的性能问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60087106/

相关文章:

java - 从网络中获取 RSS 并解析它

java - 哪种方法签名好,为什么?

c# - 如何保证 CancellationTokens 是最新的?

Java 线程 - 同步代码

java - 递归和/或 getBackground() 未按预期工作

java - 向 Java Swing 容器添加动画/过渡

java - 在 JTextArea 中查找搜索项

java - 带有继承的 JPA 映射树结构

java - 如何在 Datadog 仪表板小部件中显示正确的货币值(value)

.net - 发出信号并立即关闭 ManualResetEvent 是否安全?