简体   繁体   English

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

[英]Performance issue displaying JFreeChart points, when running in the EDT

Background:背景:

A well-known Swing best-practice requirement is that code that interacts with the Swing framework must also execute in EDT (Event Dispatch Thread).一个众所周知的 Swing 最佳实践要求是,与 Swing 框架交互的代码也必须在 EDT(事件调度线程)中执行。

I thus changed my code to have my JFreeChart-based updates to run in EDT.因此,我更改了代码,让基于 JFreeChart 的更新在 EDT 中运行。 However, a complete chart display task that usually took about 7 minutes to finish on a “normal” thread, become a several hours task when running in EDT!然而,一个完整的图表显示任务通常需要大约 7 分钟才能在“正常”线程上完成,而在 EDT 中运行时却变成了几个小时的任务!


What am I doing wrong?我究竟做错了什么? Did I misunderstood the Swing Concurrency lesson?我是否误解了 Swing 并发课程? Do I really have to run org.jfree.data.time.TimeSeries.addOrUpdate(date, double) inside EDT?我真的必须在 EDT 中运行org.jfree.data.time.TimeSeries.addOrUpdate(date, double)吗?

Please advise!请指教!


Details:细节:

Clicking a Swing GUI button, my program triggers a time-consuming task.单击 Swing GUI 按钮,我的程序会触发一项耗时的任务。 Basically, it reads a (large) file with pair-values (date, double) and then shows them by using the JFreeChart framework.基本上,它读取一个带有成对值(日期、双精度)的(大)文件,然后使用 JFreeChart 框架显示它们。

Because this is a time-consuming task, while reading and displaying data, a JProgreessBar shows user the progress status in foreground, while the chart is updated in background (user is still able to visually see every chart update, behind the progress bar).因为这是一项耗时的任务,在读取和显示数据时, JProgreessBar在前台向用户显示进度状态,而在后台更新图表(用户仍然可以在进度条后面直观地看到每个图表更新)。

This worked fine, until I decided to review the code to have my chart data being updated and displayed inside Swing EDT.这工作得很好,直到我决定查看代码以更新我的图表数据并显示在 Swing EDT 中。 Then, a complete task that usually took about 7 minutes to finish, started to take several hours to complete!然后,一个通常需要大约 7 分钟才能完成的完整任务,开始需要几个小时才能完成!


Here's the list of threads I'm using:这是我正在使用的线程列表:

1) Since the task is triggered by a Swing Button Listener, it is running in EDT. 1) 由于任务是由 Swing Button Listener 触发的,因此它在 EDT 中运行。 The JProgressBar is also running in this same thread; JProgressBar也在同一个线程中运行;

2) While showing the JProgressBar , a second (“normal”) thread is created and executed in the background. 2) 在显示JProgressBar ,会在后台创建并执行第二个(“正常”)线程。 This is where the heavy work is done.这是繁重的工作完成的地方。

It includes the update of the JProgressBar status on the other thread (by calling JProgressBar.setvalue() ) and the update of my JFreeChart chart (by calling TimeSeries.addOrUpdate(date, double) , which automatically updates a org.jfree.chart.ChartPanel ).它包括另一个线程上JProgressBar状态的更新(通过调用JProgressBar.setvalue() )和我的JFreeChart图表的更新(通过调用TimeSeries.addOrUpdate(date, double) ,它会自动更新org.jfree.chart.ChartPanel )。

Updating the chart in my second (“normal”) thread usually took about 7 minutes to finish.在我的第二个(“正常”)线程中更新图表通常需要大约 7 分钟才能完成。 Without any noticeable issue.没有任何明显的问题。


However, knowing that most Swing object methods are not "thread safe" and ChartPanel is just a Swing GUI component for displaying a JFreeChart object, I decided to run my chart update code TimeSeries.addOrUpdate(date, double) inside EDT.然而,知道大多数 Swing 对象方法都不是“线程安全的”并且ChartPanel只是一个用于显示JFreeChart对象的 Swing GUI 组件,我决定在 EDT 中运行我的图表更新代码TimeSeries.addOrUpdate(date, double)

Still running in my second “normal” thread, I tested with the following asynchronous code:仍在我的第二个“正常”线程中运行,我使用以下异步代码进行了测试:

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

but I realized my JProgressBar would reach 100% much before the chart was updated.但我意识到我的 JProgressBar 在图表更新之前会达到 100%。 I guess this was expected as displaying chart data is much slower than getting and processing the data.我想这是预期的,因为显示图表数据比获取和处理数据慢得多。

I then tried following synchronous code:然后我尝试了以下同步代码:

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

And this is where I found the performance issue: now a complete task that used to take about 7 minutes to finish, started to take hours to complete!这就是我发现性能问题的地方:现在一个完整的任务过去需要大约 7 分钟才能完成,现在开始需要几个小时才能完成!


So, my question is:所以,我的问题是:

What am I doing wrong?我究竟做错了什么? Did I misunderstood the Swing Concurrency lesson?我是否误解了 Swing 并发课程? Do I really have to run TimeSeries.addOrUpdate(date, double) inside EDT?我真的必须在 EDT 中运行 TimeSeries.addOrUpdate(date, double) 吗?

Please advise!请指教!


UPDATE:更新:
The complete code would be too large to show here, but you can find a code snapshot below.完整的代码太大而无法在此处显示,但您可以在下面找到代码快照。
Perhaps, the only thing noticeable about the code is that I use Reflection.也许,代码中唯一值得注意的是我使用了反射。 This is because I use a generic ProgressBar Class that invokes in background whatever class I send it as an argument (though this is not clearly shown in the snapshot below).这是因为我使用了一个通用的 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();
            }
        }
    }
}

This is how i solved the problem of updating the chart.这就是我解决更新图表问题的方法。

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();
}
}

The above code includes two solutions.上面的代码包括两种解决方案。 You can use either of them or both.您可以使用它们中的任何一个或两者。 Chart can be updated during data feeding.图表可以在数据馈送期间更新。 For example every 100th point and after last poit.例如,每 100 个点和最后一个点之后。 Eventually you can make external thread that updates chart after some time.最终,您可以制作在一段时间后更新图表的外部线程。 I have used updateAndWait every time instead of updateLater.我每次都使用 updateAndWait 而不是 updateLater。

In your code do not use reflections like that.在你的代码中不要使用这样的反射。 You should make interface.你应该制作接口。 For example:例如:

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

and implement it on every object that do the work:并在执行工作的每个对象上实现它:

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

then use it:然后使用它:

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

Eventualy You can make a base class for it and use polymorphism.最终你可以为它创建一个基类并使用多态。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM