Swing Multithreading. My GUI is freezing

前端 未结 4 1606
孤城傲影
孤城傲影 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: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) method.

提交回复
热议问题