5

我有这个ActionListener在 EDT 中被调用。我的 plot() 函数计算量很大,很容易需要五秒钟。它使 GUI 按预期挂起。我添加了SwingUtilities.invokeLater代码,它仍然挂起。既然我正在为我的起伏计算生成一个单独的线程,那么 GUI 不应该响应吗?

final ActionListener applyListener = new ActionListener() 
        {
            @CommitingFunction
            public void actionPerformed(ActionEvent arg0) 
            {
                /*Don't do plotting in the EDT :)*/
                SwingUtilities.invokeLater(new Runnable() 
                {
                    public void run() 
                    {
                        plot();
                    }
                });
            }
        };
4

5 回答 5

15

一点也不。InvokeLater 没有生成新线程。invokeLater 的存在是为了明确告诉 Swing“为此使用事件调度线程,但不是现在”。invoke 和 invokeLater 的存在是为了让您执行仅对来自其他线程的事件调度线程安全的操作 - 不是通过在这些线程上执行它们,而是通过告诉 EDT 执行它们。

您的 ActionListener 将运行得非常快,将 Runnable 扔到 Swing 的事件调度队列中。然后,当它到达那么远时,运行 plot() 将需要 5 秒钟。

唯一的解决方法是重构 plot()。使用SwingWorker(或类似的多线程策略,但 SwingWorker 可能是最好的)将 plot() 的逻辑实际移动到不同的线程上。该线程不能安全地绘制任何东西,因为它不是 Swing 事件调度线程,因此它的所有绘制操作都需要通过 invokeLater() 来执行。出于效率原因,您应该尝试使用从您的计算中存储的结果一次执行所有绘图操作,在一次调用后()上。

于 2010-11-19T17:42:35.423 回答
3

你做的与你认为的相反。您不是在 EDT 之外运行计算线程,而是在其中显式调用

SwingUtilities.invokeLater() 在 EDT 中将 runnable 排队等待稍后调用!您想改用SwingWorker

于 2010-11-19T17:43:26.217 回答
1

这是我为我公司的应用程序所做的,这是出于法律原因的一些伪代码,但其要点是如果屏幕无响应,它将重新启动 GUI。每当您使用 SwingUtilities 启动 EDT 时,在同一个 init 块中,创建两个观察者线程。一个线程将简单地使用 Swing 实用程序对 EDT 线程执行一项操作。另一个线程将监视第一个线程以查看第一个线程是否有响应。第一个线程只有在可以执行非常简单的命令时才会确认响应。

以正常方式运行时将 isEDTCheck 设置为 true,在调试模式下设置为 false(否则您将不断重新启动。

    if (isEDTCheck) {
        new Thread("EDTHeartbeat") {
            @Override
            public void run() {
                Runnable thisThingYouDo = new Runnable() {
                    public void run() {
                        int x = 0;
                    }
                };
                while (true) {
                    // first thread says we are waiting, aka bad state
                    edtwait=true;
                    try {
                        javax.swing.SwingUtilities.invokeAndWait(thisThingYouDo);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    // first thread says we are not waiting, good state
                    edtwait=false;
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread("EDTValidator") {
            @Override
            public void run() {
                while (true) {
                    // is first thread in bad state?
                    if (edtwait) {
                        try {
                            Thread.sleep(3000);
                            // after 3 seconds are we still in bad state?  if so, get rid of initial frame, pop up a dialog box in AWT that does no commands
                            if (edtwait) {
                                mainFrame.setVisible(false);
                                new Dialog();  
                            } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }


  public class Dialog extends Frame {
    private static final int WIDTH = 400;
    private static final int HEIGHT = 300;
    Frame f = null;
    public Dialog() {
        f = this;
        hasSomethingBeenEntered=false;
        this.setTitle("APP PROBLEM DETECTED");
        this.setSize(WIDTH, HEIGHT);
        this.setLocation((int)Toolkit.getDefaultToolkit().getScreenSize().getWidth() - myapp.width, 0);
        Panel p1 = new Panel() {
            @Override
            public void paint(final Graphics g) {
                int left = Dialog.WIDTH/2 - 45; // don't use WIDTH shadowed by Panel class
                int top = Dialog.HEIGHT/2 - 20; // same as above
                g.drawString("APP HAS DETECTED A PROBLEM", left, top);
            }
        };
        this.add("Center", p1);

        this.setAlwaysOnTop(true);
                 TextArea tb = new TextArea("APP HAS DETECTED A MAJOR PROBLEM\nIT WILL NOW RESTART IN 5 SECONDS");
        this.add(tb);
        this.setVisible(true);
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        restartApp();

    }

    private void restartApp() {
            Runtime.getRuntime().exec("cmd /c start cmd.exe /K \"cd C:\\Progra~1\\Common~1 && C:\\Progra~1\\Common~1\\MyAppDir\\myjavaapp.jar\"");
            System.exit(0);
       }
于 2012-08-02T19:49:21.197 回答
1

invokeLater 将任务添加到 GUI 工作队列。它将在所有其他任务执行完毕后被调用,但它仍然使用 GUI 线程。

我建议你看看使用 ExecutorService。

正如@Adam 建议的那样,它所做的任何实际绘图都需要通过invokeLater 完成。

于 2010-11-19T17:42:57.800 回答
1

您没有显示 plot() 函数内部的内容,但您不应该其中放置任何绘画。在新线程中计算您想要的任何内容,并在 EDT 中进行绘制。为此,最好使用SwingWorker

于 2010-11-19T17:43:04.070 回答