从 EDT (EventQueue) 停止另一个线程

Stopping another thread from the EDT (EventQueue)

所以我创建了一个带有停止按钮的基本摆动界面,单击它应该会停止计数线程。当应用程序启动时,线程实例将分配一个 runnable class 来执行计数循环并将其记录在控制台中。 runnable 接口中有一个方法将 volatile 变量设置为 false 基本上应该停止线程,我在停止按钮上调用它,但为什么它不停止循环?这是我的代码。

ParentContainer.java

public class ParentContainer extends JFrame implements ActionListener, WindowListener {
    private static final long serialVersionUID = 1L;
    private JButton btnStop;

    private static CountRunnable cr;

    public ParentContainer() {
        Container cp = getContentPane();
        cp.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10));
        btnStop = new JButton("Stop Counting");
        cp.add(btnStop);

        SymAction lSymAction = new SymAction();
        btnStop.addActionListener(lSymAction);

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setTitle("Counter");
        setSize(200, 90);
        setVisible(true);
    }

    public static void main(String[] args) {
        // Run GUI codes in Event-Dispatching thread for thread safety
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new ParentContainer(); // Let the constructor do the job
            }
        });

        cr = new CountRunnable();
        Thread t1 = new Thread(cr, "MyRunnable");
        t1.start();
    }

    class SymAction implements ActionListener
    {
        public void actionPerformed(ActionEvent evt)
        {
            Object object = evt.getSource();
            if (object == btnStop) { btnStop_actionPerformed(evt); }
        }
    }

    void btnStop_actionPerformed(ActionEvent evt)
    {
        cr.stop();
    }

    @Override
    public void windowActivated(WindowEvent arg0) { }

    @Override
    public void windowClosed(WindowEvent arg0) { }

    @Override
    public void windowClosing(WindowEvent arg0) { }

    @Override
    public void windowDeactivated(WindowEvent arg0) { }

    @Override
    public void windowDeiconified(WindowEvent arg0) { }

    @Override
    public void windowIconified(WindowEvent arg0) { }

    @Override
    public void windowOpened(WindowEvent arg0) { }

    @Override
    public void actionPerformed(ActionEvent arg0) { }
}

CountRunnable.java

public class CountRunnable implements Runnable {
    public static volatile boolean keepRunning;
    private int count = 1;

    @Override
    public void run() {
        keepRunning = true;
        while (keepRunning)
        {
            for (int i = 0; i < 100000; ++i) {
                System.out.println(count + "");
                ++count;
                // Suspend this thread via sleep() and yield control to other threads.
                // Also provide the necessary delay.
                try {
                    Thread.sleep(10); // milliseconds
                }
                catch (InterruptedException ex) {
                }
            }
        }
    }

    public void stop()
    {
        keepRunning = false;
    }
}

因为它首先要完成内层的for循环

for (int i = 0; i < 100000; ++i)

在进入外层 while 循环的下一次迭代之前再次检查 keepRunning 布尔值。

只需删除 for 循环即可。

设置停止条件时,经常检查条件很重要。目前,您有一个运行一段时间的循环,您只能在循环结束时看到检查结果。

要确保循环在迭代中结束,您可以在循环内添加以下检查。

if(!keepRunning) {
    break;
}

您还可以尝试不同的解决方案。因为这种停止线程的模式经常被使用,你也可以为你的子任务使用一个 SwingWorker 这个 class 提供了相当多的有用的实用方法来停止,并更新你的 gui 线程安全子任务(例如,您可以在图形用户界面而不是命令行中显示计数)

更改代码以使用 SwingWorker 很容易,在扩展 SwingWorker 时,它需要 2 个元素,一个 "final result" 和一个 "pending result".

SwingWorker的例子:

SwingWorker<String, String> task = new SwingWorker<String, String>(){
    private int count = 1;

    @Override
    public List<Integer> doInBackground() throws Exception {
        while (!isCancelled()) {
            for (int i = 0; i < 100000; ++i) {
                publish(count + "");
                ++count;
                Thread.sleep(10); // milliseconds
            }
        }
        return count + "";
    }

    @Override
    protected void process(List<Integer> chunks) {
        gui.setText(chunk.get(chunk.size() - 1));
    }

    @Override
    protected void done() {
        try {
            gui.setText(this.get());
        } catch(InterruptedException | ExecutionException ex) {
            ex.printSTackTrace();
        }
    }
}
task.execute();

// Later:

task.cancel(false);

注意,建议用false取消,因为当用true取消时,执行计数器的线程会被中断,导致Thread.sleep()抛出异常。

如果您的任务的目的只是计算一个数字,使用 timer(确保您导入正确的)class 可能更容易,使用 class 就像:

一样简单
int delay = 10; //milliseconds
ActionListener taskPerformer = new ActionListener() {
    int count = 0;
    public void actionPerformed(ActionEvent evt) {
        count++;
        gui.setText(count + "");
        // System.out.println(count + "");
    }
};
Timer task = new Timer(delay, taskPerformer);
task.setCoalesce(false); // SOmetimes, executions can be missed because other programs 
task.setRepeating(true);
task.start();

// Later:
task.stop();

请注意,使用此计时器解决方案,您可以通过调用 start() 重新启动它,这与我向您展示的其他解决方案不同。