如何取消SwingWorker的执行?

6

目前我有两个SwingWorker线程在后台执行任务。如果发生异常,方法停止工作,但线程仍在运行。

如果发生异常,我该如何停止执行并杀死doInBackground()的线程?

this.cancel(true)不能销毁/关闭线程。我该如何实现这一点?

@Override
protected Boolean doInBackground() throws Exception {
        try {
            while (true) {
                //some code here                   
                return true;
            }
        } catch (Exception e) {       
            this.cancel(true); //<-- this not cancel the thread               
            return false;
        }
    }

我在Netbeans的调试中看到了这些线程。
'AWT-EventQueue-0' em execução
'AWT-Windows' em execução
'SwingWorker-pool-1-thread-1' em execução
'SwingWorker-pool-1-thread-2' em execução

//*em execução = in execution

дҪ иғҪзЎ®и®ӨdoInBackground()еңЁwhile(true)еҫӘзҺҜеҶ…йғЁиҝ”еӣһиҖҢдёҚйҳ»еЎһеҗ—пјҹ - jfpoilpret
是的,我调试了代码并进入异常返回false。 - Renato Dinhani
6个回答

12

正如jzd所提到的,有一个名为cancel(boolean mayInterruptIfRunning)的方法; 例如

编辑:使用cancel(true);时,您必须(始终)捕获一个异常java.util.concurrent.CancellationException

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

public class SwingWorkerExample extends JFrame implements ActionListener {

    private static final long serialVersionUID = 1L;
    private final JButton startButton, stopButton;
    private JScrollPane scrollPane = new JScrollPane();
    private JList listBox = null;
    private DefaultListModel listModel = new DefaultListModel();
    private final JProgressBar progressBar;
    private mySwingWorker swingWorker;

    public SwingWorkerExample() {
        super("SwingWorkerExample");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().setLayout(new GridLayout(2, 2));
        startButton = makeButton("Start");
        stopButton = makeButton("Stop");
        stopButton.setEnabled(false);
        progressBar = makeProgressBar(0, 99);
        listBox = new JList(listModel);
        scrollPane.setViewportView(listBox);
        getContentPane().add(scrollPane);
        //Display the window.
        pack();
        setVisible(true);
    }
//Class SwingWorker<T,V> T - the result type returned by this SwingWorker's doInBackground
//and get methods V - the type used for carrying out intermediate results by this SwingWorker's 
//publish and process methods

    private class mySwingWorker extends javax.swing.SwingWorker<ArrayList<Integer>, Integer> {
//The first template argument, in this case, ArrayList<Integer>, is what s returned by doInBackground(), 
//and by get(). The second template argument, in this case, Integer, is what is published with the 
//publish method. It is also the data type which is stored by the java.util.List that is the parameter
//for the process method, which recieves the information published by the publish method.

        @Override
        protected ArrayList<Integer> doInBackground() {
//Returns items of the type given as the first template argument to the SwingWorker class.
            if (javax.swing.SwingUtilities.isEventDispatchThread()) {
                System.out.println("javax.swing.SwingUtilities.isEventDispatchThread() returned true.");
            }
            Integer tmpValue = new Integer(1);
            ArrayList<Integer> list = new ArrayList<Integer>();
            for (int i = 0; i < 100; i++) {
                for (int j = 0; j < 100; j++) { //find every 100th prime, just to make it slower
                    tmpValue = FindNextPrime(tmpValue.intValue());
//isCancelled() returns true if the cancel() method is invoked on this class. That is the proper way
//to stop this thread. See the actionPerformed method.
                    if (isCancelled()) {
                        System.out.println("SwingWorker - isCancelled");
                        return list;
                    }
                }
//Successive calls to publish are coalesced into a java.util.List, which is what is received by process, 
//which in this case, isused to update the JProgressBar. Thus, the values passed to publish range from 
//1 to 100.
                publish(new Integer(i));
                list.add(tmpValue);
            }
            return list;
        }//Note, always use java.util.List here, or it will use the wrong list.

        @Override
        protected void process(java.util.List<Integer> progressList) {
//This method is processing a java.util.List of items given as successive arguments to the publish method.
//Note that these calls are coalesced into a java.util.List. This list holds items of the type given as the
//second template parameter type to SwingWorker. Note that the get method below has nothing to do with the 
//SwingWorker get method; it is the List's get method. This would be a good place to update a progress bar.
            if (!javax.swing.SwingUtilities.isEventDispatchThread()) {
                System.out.println("javax.swing.SwingUtilities.isEventDispatchThread() + returned false.");
            }
            Integer percentComplete = progressList.get(progressList.size() - 1);
            progressBar.setValue(percentComplete.intValue());
        }

        @Override
        protected void done() {
            System.out.println("doInBackground is complete");
            if (!javax.swing.SwingUtilities.isEventDispatchThread()) {
                System.out.println("javax.swing.SwingUtilities.isEventDispatchThread() + returned false.");
            }
            try {
//Here, the SwingWorker's get method returns an item of the same type as specified as the first type parameter
//given to the SwingWorker class.
                ArrayList<Integer> results = get();
                for (Integer i : results) {
                    listModel.addElement(i.toString());
                }
            } catch (Exception e) {
                System.out.println("Caught an exception: " + e);
            }
            startButton();
        }

        boolean IsPrime(int num) { //Checks whether a number is prime
            int i;
            for (i = 2; i <= num / 2; i++) {
                if (num % i == 0) {
                    return false;
                }
            }
            return true;
        }

        protected Integer FindNextPrime(int num) { //Returns next prime number from passed arg.       
            do {
                if (num % 2 == 0) {
                    num++;
                } else {
                    num += 2;
                }
            } while (!IsPrime(num));
            return new Integer(num);
        }
    }

    private JButton makeButton(String caption) {
        JButton b = new JButton(caption);
        b.setActionCommand(caption);
        b.addActionListener(this);
        getContentPane().add(b);
        return b;
    }

    private JProgressBar makeProgressBar(int min, int max) {
        JProgressBar progressBar1 = new JProgressBar();
        progressBar1.setMinimum(min);
        progressBar1.setMaximum(max);
        progressBar1.setStringPainted(true);
        progressBar1.setBorderPainted(true);
        getContentPane().add(progressBar1);
        return progressBar1;
    }

    private void startButton() {
        startButton.setEnabled(true);
        stopButton.setEnabled(false);
        System.out.println("SwingWorker - Done");
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if ("Start" == null ? e.getActionCommand() == null : "Start".equals(e.getActionCommand())) {
            startButton.setEnabled(false);
            stopButton.setEnabled(true);
// Note that it creates a new instance of the SwingWorker-derived class. Never reuse an old one.
            (swingWorker = new mySwingWorker()).execute(); // new instance
        } else if ("Stop" == null ? e.getActionCommand() == null : "Stop".equals(e.getActionCommand())) {
            startButton.setEnabled(true);
            stopButton.setEnabled(false);
            swingWorker.cancel(true); // causes isCancelled to return true in doInBackground
            swingWorker = null;
        }
    }

    public static void main(String[] args) {
// Notice that it kicks it off on the event-dispatching thread, not the main thread.
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                SwingWorkerExample swingWorkerExample = new SwingWorkerExample();
            }
        });
    }
}

isCancelled() 方法在 cancel() 方法之后返回 true,但为什么线程 'SwingWorker-pool-1-thread-1' 仍然显示为正在执行? - Renato Dinhani
好的解释,所有都在这里了(+1)。这个例子非常精彩,我喜欢它,谢谢 :) - Boro
这个有趣的例子使用 JProgressBar 来解释逻辑,不错 :-) - nIcE cOw
点击取消后,它不会将不完整的结果输出到listModel。日志:doInBackground已完成 SwingWorker - 完成 doInBackground已完成 SwingWorker - 已取消 捕获异常:java.util.concurrent.CancellationException SwingWorker - 完成 - yalov
如果在预期时间之外调用了'done()',则可以取消SwingWorker。请参考以下链接:https://dev59.com/hG025IYBdhLWcg3wHSGn - yalov

10

默认情况下,SwingWorker 会重用工作线程,所以即使 doInBackground() 已经返回,仍然可能看到执行该方法的线程。

可以通过查看 NetBeans 报告的线程名称来确定此事实:SwingWorker-pool-1-thread-1,其中的 poolSwingWorker 管理。

如果您想要更多的控制,还可以将 SwingWorker 实例传递给 Executor

有关更多信息,请参见 SwingWorkerExecutor 的 javadoc。

此外,SwingWorker.cancel() 不应该从 doInBackground() 中调用,而是应该从另一个线程(通常是 EDT)中调用,例如当用户在进度对话框中点击“取消”按钮时。


这不是重用,每次我点击按钮以在后台运行的进程,它都会创建一个新线程。我将尝试从另一个线程调用以查看会发生什么。 - Renato Dinhani
很难说,但你无法完全掌握SwingWorker处理线程池和将给定的SwingWorker实例分配到池中的线程的方式。正如我所说,如果您想要完全控制,则可以使用现有的Executor实现之一,或者构建自己的实现,并将您的SwingWorker实例传递给其execute方法。 - jfpoilpret
同意,但是Executor加上SwingWorker仍然在前25名http://bugs.sun.com/top25_bugs.do。如果你真的想要多线程,那么你必须给你的线程命名。为SwingWorker添加PropertyChangeListener,这样你就可以知道SwingWorker的状态。要小心Executor启动的同时线程数量,因为Executor不知道SwingWorker是否结束。为了避免任何失败,从这里放置布尔值,然后只检查来自PropertyChangeListener的值与done()的最后一行相匹配,返回true确认线程已经结束而没有任何故障。 - mKorbel
如果我没记错的话,SwingWorker 的顶级错误与它与 Executor 一起使用无关,实际上是一个“纯粹”的 SwingWorker 错误。 - jfpoilpret
是的,毕竟我想放弃SwingWorker并找到另一种方法。我只是对SwingWorker感到好奇,但如果我需要完全控制,最好使用Executors或其他东西。谢谢大家。 - Renato Dinhani
Executor是一个简单的接口,我不认为它会有bug;-) 我说的是你可以使用SwingWorker与Executor(而不仅仅是在其上调用execute()),如果标准的Executor不起作用或不符合你的需求,你可以实现自己的Executor。这只是我的(最后)两分钱。 - jfpoilpret

4

有一个cancel()方法。您的代码需要注意这一点。如果在异常之后继续运行,那么您的代码似乎忽略了不应该被忽略的异常。


是的,我看到类似这样的东西了,但是这些方法是在异常内部调用的,使用了 this.cancel(true); 或者是在调用 SwingWorker 的 execute 方法的类中调用的? - Renato Dinhani
1
cancel方法是为调用execute的对象而设计的。如果SwingWorker由于异常需要停止执行,则必须正确处理该异常。 - jzd

4

2

0

直到SwingWorker被修复http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6826514 这里有一个简单的(经过测试的)版本,具有与SwingWorker类似的基本功能

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tools;

import java.util.LinkedList;
import java.util.List;
import javax.swing.SwingUtilities;

/**
 *
 * @author patrick
 */
public abstract class MySwingWorker<R,P> {

    protected abstract R doInBackground() throws Exception;
    protected abstract void done(R rvalue, Exception ex, boolean canceled);
    protected void process(List<P> chunks){}
    protected void progress(int progress){}

    private boolean cancelled=false;
    private boolean done=false;
    private boolean started=false;
    final private Object syncprogress=new Object();
    boolean progressstate=false;
    private int progress=0;
    final private Object syncprocess=new Object();
    boolean processstate=false;
    private LinkedList<P> chunkes= new LinkedList<>();

    private Thread t= new Thread(new Runnable() {
        @Override
        public void run() {
            Exception exception=null;
            R rvalue=null;
            try {
                rvalue=doInBackground();
            } catch (Exception ex) {
                exception=ex;
            }

            //Done:
            synchronized(MySwingWorker.this)
            {
                done=true;
                final Exception cexception=exception;
                final R crvalue=rvalue;
                final boolean ccancelled=cancelled;

                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        done(crvalue, cexception, ccancelled);
                    }
                });
            }

        }
    });    

    protected final void publish(P p)
    {
        if(!Thread.currentThread().equals(t))
            throw new UnsupportedOperationException("Must be called from worker Thread!");
        synchronized(syncprocess)
        {
            chunkes.add(p);
            if(!processstate)
            {
                processstate=true;
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        List<P> list;
                        synchronized(syncprocess)
                        {
                            MySwingWorker.this.processstate=false;
                            list=MySwingWorker.this.chunkes;
                            MySwingWorker.this.chunkes= new LinkedList<>();
                        }
                        process(list);
                    }
                });
            }
        }
    }

    protected final void setProgress(int progress)
    {
        if(!Thread.currentThread().equals(t))
            throw new UnsupportedOperationException("Must be called from worker Thread!");
        synchronized(syncprogress)
        {
            this.progress=progress;
            if(!progressstate)
            {
                progressstate=true;
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        int value;
                        //Acess Value
                        synchronized(syncprogress)
                        {
                            MySwingWorker.this.progressstate=false;
                            value=MySwingWorker.this.progress;
                        }
                        progress(value);
                    }
                });
            }
        }
    }

    public final synchronized void execute()
    {
        if(!started)
        {
            started=true;
            t.start();
        }
    }

    public final synchronized boolean isRunning()
    {
        return started && !done;
    }

    public final synchronized boolean isDone()
    {
        return done;
    }

    public final synchronized boolean isCancelled()
    {
        return cancelled;
    }

    public final synchronized void cancel()
    {
        if(started && !cancelled && !done)
        {
            cancelled=true;
            if(!Thread.currentThread().equals(t))
                t.interrupt();
        }
    }

}

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