停止/取消SwingWorker线程?

27

我正在尝试找出如何在按下按钮时停止 SwingWorker 线程的运行。我已经搜索了一些资料,但现在还不太清楚如何实现。目前这是我所拥有的:

new MySwingWorkerClass(args).execute();
我接下来要创建一个按钮,我想用它来停止线程:
button = new JButton("Stop");
button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) 
        {
        // Stop the swing worker thread
        }
});

我已经搜索过了,找到了cancel方法。但我不明白如何使用它来停止我的Swing Worker。我尝试了以下代码,但没有成功:

SwingWorker.cancel(true);

1
cancel不是一个静态方法,所以最后一行甚至无法编译..保留对实例的引用(如@Jonas建议的那样)是正确的方式。 - kleopatra
4个回答

45

你需要保留对你的SwingWorker的引用,然后使用该引用来取消工作线程。

MySwingWorker myWorker = new MySwingWorkerClass(args).execute();

button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) 
    {
        // Stop the swing worker thread
        myWorker.cancel(true);
    }
});

这里有一个完整的例子:

输入图像描述

public class WorkerDemo extends JFrame {
    private boolean isStarted = false;
    private JLabel counterLabel = new JLabel("Not started");
    private Worker worker = new Worker();
    private JButton startButton = new JButton(new AbstractAction("Start") {

        @Override
        public void actionPerformed(ActionEvent arg0) {
            if(!isStarted) { 
                worker.execute();
                isStarted = false;
            }
        }

    });
    private JButton stopButton = new JButton(new AbstractAction("Stop") {

        @Override
        public void actionPerformed(ActionEvent arg0) {
            worker.cancel(true);
        }

    });

    public WorkerDemo() {

        add(startButton, BorderLayout.WEST);
        add(counterLabel, BorderLayout.CENTER);
        add(stopButton, BorderLayout.EAST);
        pack();
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    class Worker extends SwingWorker<Void, Integer> {

        int counter = 0;

        @Override
        protected Void doInBackground() throws Exception {
            while(true) {
                counter++;
                publish(counter);
                Thread.sleep(60);
            }
        }

        @Override
        protected void process(List<Integer> chunk) {

            // get last result
            Integer counterChunk = chunk.get(chunk.size()-1);

            counterLabel.setText(counterChunk.toString());
        }

    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new WorkerDemo();
            }

        });
    }

}

2
我很好奇为什么这个答案还没有被接受。它看起来是正确和最明显的答案。 - eternaln00b
1
刚刚注意到一个小错别字:在startButton中,你可能想将isStarted属性设置为true :-) - kleopatra
2
实际上,这没有什么区别:API文档说明_SwingWorker只设计为执行一次。多次执行SwingWorker不会导致调用doInBackground方法两次。 - kleopatra
swingworker.cancel(true)能确保swingworker被取消/终止吗?如果我在其中放置一个while循环并尝试从主类取消工作器,循环不会停止。 - yolob 21

16

1
示例:isCancelled()的用法请参考以下链接:https://dev59.com/eVfUa4cB1Zd3GeqPI4dE#6114890。+1 - mKorbel
@mre 感谢您的帮助。不幸的是,我仍然对最佳方法感到困惑,因为在我的代码中无法定期检查 isCancelled 的状态。有没有一种方法可以完全终止它? - user843337
我想把它发布出来,但有很多类。基本上,我调用一个方法,在循环中运行一些代码,这可能需要几个小时才能运行,因为需要处理大量的数据。这正是我需要一个停止按钮来杀死线程或中断它或其他什么原因。 - user843337
@TheCrazyChimp,你需要间歇性地检查取消标志,否则你将永远不知道被请求停止了。 - mre
@TheCrazyChimp:“我调用一个方法,在循环中运行一些代码。” 你可以做两件事情:1)在循环中的某个地方调用isCancelled()(你可能需要进行一些重构,以便执行循环的方法可以访问SwingWorker实例)。2)在循环中的某个地方调用Thread.sleep()。(这是有效的,因为Thread.sleep()会检查当前线程是否已被取消。) - vaughandroid
1
谢谢您提供这个链接 - while(!isCancelled()) {...} 循环帮了我很多! - Jochen Birkle

2

你可以尝试重写done()方法,将其包含在try catch中,并捕获java.util.concurrent.CancellationException。例如:

。 。 。

@Overide
done() {
   try {
     get();
   } catch (CancellationException e) {
       // Do your task after cancellation
   }
}

. . .


通常认为在程序流程中使用异常是一种不良实践,ActionListeners可能是更好的解决方案。 - element11

2

我认为每次停止后都需要重新启动计数器。

对于startButton,您需要重新启动worker,如

private JButton startButton = new JButton(new AbstractAction("Start") {

        @Override
        public void actionPerformed(ActionEvent arg0) {
            if(!isStarted) { 
                worker = new Worker();     // New line
                worker.execute();
                isStarted = false;
            }
        }
    });

停止操作可以使用

worker.setStopFlag(); // in stopButton actionPerformed block.

在Worker类中,私有变量stopFlag被定义为false。
然后添加以下内容:
if( stopFlag)
break;

在Thread.sleep(60);之后,最终将stopFlag的setter放在Worker类的结尾处。

void setStopFlag(){
    stopFlag = true;
}

顺便提一下,如果您想要异常退出,可以使用cancel(true)函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接