【问题标题】:Swing Multithreading. My GUI is freezing摆动多线程。我的 GUI 冻结
【发布时间】:2015-11-21 15:25:43
【问题描述】:

免责声明:我没有将我的程序用于任何恶意,即使它的名称是 Spambot。我只是用来练习的。

编辑:我的问题是,如果我按下一个按钮,GUI 就会冻结,因此我无法按下另一个按钮,直到第一个按钮完成其工作。我该如何解决这个问题?

我创建了一个类(SpambotGUI),它基本上是一个包含 3 个 JButton 的 JFrame。这是它的代码:

public class SpambotGUI extends JPanel implements ActionListener {
    private static final long serialVersionUID = 1L;
    static JButton button1 = new JButton("Spam first file");
    static JButton button2 = new JButton("Spam second file");
    static JButton button3 = new JButton("Stop");

    public SpambotGUI() throws AWTException {
        button1.addActionListener(this);
        button2.addActionListener(this);
        button3.addActionListener(this);
        button1.setActionCommand("spam1");
        button2.setActionCommand("spam2");
        button3.setActionCommand("stop");
        button1.setMnemonic(KeyEvent.VK_F7);
        button2.setMnemonic(KeyEvent.VK_F8);
        button3.setMnemonic(KeyEvent.VK_F9);
        button3.setToolTipText("Stop the program");
        add(button1, BorderLayout.WEST);
        add(button2, BorderLayout.CENTER);
        add(button3, BorderLayout.SOUTH);

    }

    public void actionPerformed(ActionEvent e) {
        System.out.println(java.awt.EventQueue.isDispatchThread());
        if ((e.getActionCommand()).equals("spam1")) {
            try {
                Spambot.Start("data/spambotLines1.txt");
            } catch (FileNotFoundException | AWTException | InterruptedException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
        } else if ((e.getActionCommand()).equals("spam2")) {
                   try {
                       Spambot.Start("data/spambotLines2.txt");
                   } catch (FileNotFoundException | AWTException | InterruptedException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                   }
               } else if ((e.getActionCommand()).equals("stop")) {
                          Spambot.stopped = true;
                          Spambot.thread.interrupt();
                      }

    }

    public static void CreateGUI() throws AWTException {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        SpambotGUI buttons = new SpambotGUI();
        buttons.setOpaque(true);
        frame.setContentPane(buttons);

        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String args[]) throws Exception {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    CreateGUI();
                } catch (AWTException e) {
                    e.printStackTrace();
                }

            }
        });
    }

}

我还有一个Spambot 类,它包含以下内容:在Start 方法中,我有一个循环,包含不相关的东西和thread.Sleep-s(我创建了一个名为thread 的新Thread(),这就是为什么它在SpambotGUI) 中用小写字母t 拼写,并且循环应该一直运行到Spambot 中的stopped boolean 为假。如果我在 GUI 中按下 Stop 按钮,后者将被设置为 false。问题是当Start 中的循环正在运行时,我无法单击GUI 中的任何按钮。我在互联网上阅读了这个之后,我得出的结论是我应该在这里使用多线程。

问题是,我只是不知道它应该如何工作。我尝试在我的Spambot 类中实现Runnable,然后从SpambotGUI 调用run() 方法,但没有任何改变。

有人知道我应该在这里做什么吗?

编辑:这是Spambot 类的一部分:

public class Spambot{

    private static Robot robot;
    public static Thread thread = new Thread();
    public static boolean stopped = false;

    public static void main(String... args) throws Exception {

    }

    public static void Start(String path) throws AWTException, InterruptedException, FileNotFoundException {
        Scanner input = new Scanner(new FileReader(path));
        Spambot keyboard = new Spambot();
        Random rand = new Random();
        robot.keyPress(KeyEvent.VK_ALT);
        thread.sleep(150);
        robot.keyPress(KeyEvent.VK_TAB);
        thread.sleep(150);
        robot.keyRelease(KeyEvent.VK_TAB);
        robot.keyRelease(KeyEvent.VK_ALT);
        thread.sleep(500);
        while (input.hasNextLine() && !stopped) {
            keyboard.type(input.nextLine());
            thread.sleep(rand.nextInt(1500)+1000);
            robot.keyPress(KeyEvent.VK_ENTER);
            robot.keyRelease(KeyEvent.VK_ENTER);
        }
        input.close();
    }


    public Spambot() throws AWTException {
        Spambot.robot = new Robot();
    }

    public Spambot(Robot robot) {
        Spambot.robot = robot;
    }
}

【问题讨论】:

  • 听起来您的 Start1/2 代码实际上并未在其他线程中运行。
  • 请贴出线程代码
  • 我和其他班级一起编辑了我的帖子。
  • don't just copy in your entire program!。也很清楚你要什么?
  • 我认为是(另外,我删除了程序中不相关的部分)。我的问题是,如果我按下一个按钮,GUI 就会冻结,因此在第一个按钮完成工作之前我无法按下另一个按钮。

标签: java multithreading swing


【解决方案1】:

您实际上可能需要启动一个新的Thread,因此阻塞操作不会对您的应用程序 GUI 造成太大影响。但是,该更新 GUI 中的操作应由原始事件调度线程执行。

作为pointed in other answers,这里的主要罪犯似乎是使用Thread.sleep()。在事件调度线程中执行时会导致 GUI 变得无响应(在完成执行事件侦听器代码之前不会接受您的输入或重绘)。但是,如果在其他线程中使用 Thread.sleep() 是可以接受的(这不会冻结您的 GUI)。

怎么做

首先:在单独的线程中启动您的阻塞处理代码。

public void actionPerformed(ActionEvent e) {
    if ((e.getActionCommand()).equals("spam1")) {
        new Thread(){
            @Override
            public void run() {
                try {
                    Spambot.Start("data/firstfile.txt");
                } catch (FileNotFoundException | InvocationTargetException | 
                        AWTException | InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }.start();
    }
    // ... rest of conditions

}

其次,延迟之间的每个单独的 GUI 更新都应该在事件调度线程中完成。

EventQueue.invokeAndWait(new Runnable(){
    public void run() {
        robot.keyPress(KeyEvent.VK_ALT);
    };
});

由于完成的所有更新都在robot.keyPress() 调用中,一个不错的选择可能是封装并在方法中重用。请注意,在内部类中使用的局部变量和参数应定义为 final(因此它们在方法的堆栈框架之外可用)

private static void invokeRobotInAWT(final Integer ... ke) throws InvocationTargetException, InterruptedException {
    EventQueue.invokeAndWait(new Runnable(){
        public void run() {
            for (int currentEvent : ke) {
                robot.keyPress(currentEvent);
            }
        };
    });
}

public static void Start(String path) throws AWTException, InterruptedException, FileNotFoundException, InvocationTargetException {
    try (Scanner input = new Scanner(new FileReader(path));) {
        Spambot keyboard = new Spambot();
        Random rand = new Random();
        invokeRobotInAWT(KeyEvent.VK_ALT);
        Thread.sleep(150);
        invokeRobotInAWT(KeyEvent.VK_TAB);
        Thread.sleep(150);
        invokeRobotInAWT(KeyEvent.VK_TAB, KeyEvent.VK_ALT);
        Thread.sleep(500);
        while (input.hasNextLine() && !stopped) {
            // @@@ Would need extra threading here?
            keyboard.type(input.nextLine());
            Thread.sleep(rand.nextInt(1500)+1000);
            invokeRobotInAWT(KeyEvent.VK_ENTER, KeyEvent.VK_ENTER);
        }
    } finally {
        // input closed by try-with-resources
    }
}

已编辑:糟糕。 SwingWorker 我弄错了。实际上可能就足够了。

注意:Swing 中有帮助组件,可以让我们免于复杂且容易出错的线程处理。您实际上可能会使用SwingWorker,其中覆盖的doInBackground() 方法(在工作线程中)遍历文件、执行暂停并发送击键(调用publish(Integer)),由EDT 在覆盖的@ 中处理987654331@方法。

【讨论】:

  • 我明白了。我认为我了解 SwingWorker 和 Swing Concurrence 是什么,我只是无法使用它们。如果我能分析一些代码 sn-p,我将不胜感激。
  • @Smodics 刚刚添加了我的实际解决方案。我希望这会有所帮助。
  • 看起来有点复杂,理解肯定需要一些时间。不管怎样,谢谢!对此,我真的非常感激。如果有什么不清楚的地方我会问。
  • @Smodics 更新了,我弄错了几处(启动器代码,SwingWorker)。
【解决方案2】:

您没有使用任何线程,而是仅使用 Thread.sleep() 方法。它只是一个正常的睡眠程序。因此,您的 GUI 将被阻止,直到您的操作在 start1 和 start2 中完成。

【讨论】:

  • Thread.sleep 只是让你的主线程等待提到的秒数。它没有创建线程。
  • 我已经明白了。如果不是 Thread.sleep(),我仍然不知道我应该使用什么。
  • Marko's earlier reply 让这个变得多余?
【解决方案3】:

您应该在任何 GUI 应用程序中使用的不是多线程,而是 事件驱动编程。这意味着永远不要在事件处理程序中执行长时间运行的循环,并且永远不要永远同时调用Thread.sleep

相反,必须在 Swing Timer 上安排延迟的 GUI 操作,并且必须将循环体放入您提交给计时器的处理程序中。

如果您没有延迟操作,而是真正需要长时间运行的任务(这意味着您需要进行大量计算或等待 I/O),那么您才需要后台线程来完成工作.在这种情况下,您将使用 SwingWorker 将任务结果传回 GUI。

【讨论】:

  • 其他人都指向这个答案,但老实说,我不太了解它。更准确地说,你的第二段是我坚持的。这是我第一次使用 GUI,因此我不知道 GUI 操作或 Swing Timer 是什么。我也不知道什么是处理程序(我觉得我应该知道这个,但我不知道)以及如何将它提交给计时器。你能解释一下这对初学者更友好吗?
  • 我试图为您提供进一步研究的指导。您需要阅读初学者教程,例如 Oracle 提供的教程。他们在大量的篇幅中解释了所有这些术语,这完全不适合单个 StackOverflow 答案。
  • 我明白了。不过还是谢谢你的回答。
【解决方案4】:

我认为重要的是你要明白你的程序逻辑和GUI 根本不应该在同一个线程上运行。当您的程序忙于执行任务时,您不希望您的GUI 冻结。 Java 解决这个问题的方法是 Event Dispatch Thread (EDT)。 你这样做:

public static void main(String args[]) {
    EventQueue.invokeLater(new Runnable() {
        public void run() {
            //Your GUI code here
        }
    });
}

这里有更多关于 EDT 的信息:https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html

【讨论】:

  • 它不工作。我什至尝试打印出“isDispatchThread()”的值,它显示“true”,但 GUI 仍然冻结。我做错了什么,不是吗?
  • 您可能正在 EDT 中运行 thread.sleep(),这会导致您的 GUI 冻结。
  • 可能的。我将如何在 EDT 中运行它?
  • 如果您发布更新后的代码,我们可能会更好地为您提供进一步的帮助。
猜你喜欢
  • 2014-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-12
  • 1970-01-01
  • 2010-09-20
  • 1970-01-01
相关资源
最近更新 更多