Swing Multithreading. My GUI is freezing

前端 未结 4 1597
孤城傲影
孤城傲影 2021-01-28 05:41

Disclaimer: I\'m not using my program for anything malicious, even though it\'s name is Spambot. I\'m only using it for practice.

Edit: My problem is th

相关标签:
4条回答
  • 2021-01-28 06:14

    I think it is important that you understand that your program logic and GUI should never be run on the same thread at all. When your program is busy preforming tasks you do not want your GUI to freeze up. The way Java solves this is the Event Dispatch Thread (EDT). You do this like so:

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

    More info on the EDT here: https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html

    0 讨论(0)
  • 2021-01-28 06:20

    You are not using any threading instead you just using a Thread.sleep() method. Its just a normal program with sleep. So your GUI will be blocked until your operation completes in start1 and start2.

    0 讨论(0)
  • 2021-01-28 06:25

    You might actually have to launch a new Thread, so the blocking operations do not affect your application GUI too much. However, operations within that update the GUI should be executed by the original Event Dispatch Thread.

    As pointed in other answers, here the main offender seems to be the use of Thread.sleep(). Which when executed in the Event Dispatch Thread will cause the GUI to become irresponsive (won't accept your input or redraw until it has finished executing your event listener code). However that Thread.sleep() is acceptable if used in other Threads (which wont freeze your GUI).

    How to do it

    First: Launch your blocking handling code in a separate Thread.

    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
    
    }
    

    Second, every individual GUI update between delays, should be done in the Event Dispatch Thread.

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

    As all the updates done are in robot.keyPress() invocations, a good choice might be to encapsulate and re-use in a method. Note that local variables and arguments that are used inside inner class should be defined as final (so they are made available outside the stackframe of the method)

    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
        }
    }
    

    Edited: Oops. I got it wrong with SwingWorker. Might actually be adequate.

    Note: There are helper components in Swing that might spare us from complex and error-prone thread handling. You might actually use a SwingWorker where your overridden doInBackground() method (in the worker thread) traverses the file, does the pauses, and sends the keystrokes (call publish(Integer)), to be processed by the EDT in the overridden process(List<Integer>) method.

    0 讨论(0)
  • 2021-01-28 06:30

    What you should be using in any GUI application is not multithreading, but event-driven programming. That means never execute long-running loops inside an event handler, and never, ever, call Thread.sleep in one.

    Instead, a delayed GUI action must be scheduled on a Swing Timer, and the loop body must be put into the handler which you submit to the timer.

    If you don't have a delayed action, but truly a long-running task (that would mean you do a lot of computation or wait for I/O), only then would you need a background thread to do the work. In that case you would use the SwingWorker to communicate the task's results back to the GUI.

    0 讨论(0)
提交回复
热议问题