10

我一直在努力解决 SwingWorker 吃掉后台任务中抛出的任何异常的可用性问题,例如,在这个 SO thread 上描述的。该线程很好地描述了问题,但没有讨论恢复原始异常。

我收到的小程序需要向上传播异常。但我什至无法抓住它。我正在使用此博客条目中的 SimpleSwingWorker 包装类来专门尝试解决此问题。这是一个相当小的课程,但我会在最后重新发布它以供参考。

调用代码看起来很像

try {
    // lots of code here to prepare data, finishing with
    SpecialDataHelper helper = new SpecialDataHelper(...stuff...);
    helper.execute();  // this will call get+done on the actual worker
} catch (Throwable e) {
    // used "Throwable" here in desperation to try and get
    // anything at all to match, including unchecked exceptions
    //
    // no luck, this code is never ever used :-(
}

包装:

class SpecialDataHelper extends SimpleSwingWorker {
    public SpecialDataHelper (SpecialData sd) {
        this.stuff = etc etc etc;
    }
    public Void doInBackground() throws Exception {
        OurCodeThatThrowsACheckedException(this.stuff);
        return null;
    }
    protected void done() {
        // called only when successful
        // never reached if there's an error
    }
}

的特点SimpleSwingWorker是会自动调用实际的 SwingWorkerdone()/get()方法。从理论上讲,这会重新引发后台发生的任何异常。在实践中,什么都没有被抓到,我什至不知道为什么。

SimpleSwingWorker 类,供参考,为简洁起见,没有省略:

import java.util.concurrent.ExecutionException;
import javax.swing.SwingWorker;

/**
 * A drop-in replacement for SwingWorker<Void,Void> but will not silently
 * swallow exceptions during background execution.
 *
 * Taken from http://jonathangiles.net/blog/?p=341 with thanks.
 */
public abstract class SimpleSwingWorker {
    private final SwingWorker<Void,Void> worker =
        new SwingWorker<Void,Void>() {
            @Override
            protected Void doInBackground() throws Exception {
                SimpleSwingWorker.this.doInBackground();
                return null;
            }

            @Override
            protected void done() {
                // Exceptions are lost unless get() is called on the
                // originating thread.  We do so here.
                try {
                    get();
                } catch (final InterruptedException ex) {
                    throw new RuntimeException(ex);
                } catch (final ExecutionException ex) {
                    throw new RuntimeException(ex.getCause());
                }
                SimpleSwingWorker.this.done();
            }
    };

    public SimpleSwingWorker() {}

    protected abstract Void doInBackground() throws Exception;
    protected abstract void done();

    public void execute() {
        worker.execute();
    }
}
4

3 回答 3

25

忘记你的包装器,它会吃掉异常,而 SwingWorker 不会。以下是应该如何使用 SwingWorker,以及应该如何处理从后台任务抛出的特定异常:

class MeaningOfLifeFinder extends SwingWorker<String, Object> {
    @Override
    public String doInBackground() throws SomeException {
        return findTheMeaningOfLife();
    }

    @Override
    protected void done() { // called in the EDT. You can update the GUI here, show error dialogs, etc.
        try { 
            String meaningOfLife = get(); // this line can throw InterruptedException or ExecutionException
            label.setText(meaningOfLife);
        } 
        catch (ExecutionException e) {
            Throwable cause = e.getCause(); // if SomeException was thrown by the background task, it's wrapped into the ExecutionException
            if (cause instanceof SomeException) {
                // TODO handle SomeException as you want to
            }
            else { // the wrapped throwable is a runtime exception or an error
                // TODO handle any other exception as you want to
            }
        }
        catch (InterruptedException ie) {
            // TODO handle the case where the background task was interrupted as you want to
        }
    }
}
于 2012-12-18T23:01:40.957 回答
3

包装器似乎按预期工作。done()但是,如果发生异常,它的实现将永远不会调用。这不适用于许多情况。调用它可能更简单get()done()这将抛出任何发生在doInBackground().

不确定您的示例的结构如何,但它在没有 EDT 的应用程序中不起作用。所以包装工人执行SwingUtilities.invokeLater确实有帮助,即:

SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        new SpecialDataHelper().execute();
    }
});

以下示例确实打印了异常堆栈跟踪:

public class Tester {

    static class SpecialDataHelper extends SimpleSwingWorker {
        public SpecialDataHelper () {
        }
        public Void doInBackground() throws Exception {
            throw new Exception("test");
        }
        protected void done() {
        }
    }

    public static void main(String[] args) {
        try{
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    new SpecialDataHelper().execute();
                }
            });
        } catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

还要考虑这个简单的示例,该示例演示如何在doInBackground()不使用包装器的情况下获取发生的异常。包装器只是一个帮手,以防您忘记调用get().

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class Tester {
    static class Worker extends SwingWorker<Void,Void> {
        @Override
        protected Void doInBackground() throws Exception {
            throw new Exception("test");
        }
        @Override
        protected void done() {
            try {
                get();
                JOptionPane.showMessageDialog(null, "Operation completed");
            } catch (Exception ex) {
                JOptionPane.showMessageDialog(null, "Operation failed");
            } 
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new Worker().execute();
            }
        });         
    }
}
于 2012-12-18T21:46:06.110 回答
2

我想每个人都把这件事弄得太复杂了。试试这个:

String myResult="notSet";
try {
    // from your example above
    helper.execute();  // this will call get+done on the actual worker
    myResult=helper.get();
} catch (Exception e) {
// this section will be invoked if your swingworker dies, and print out why...
     System.out.println("exception ");
     e.printStackTrace() ;
     myResult="Exception "+e.getMessage();
}
return myResult;

您抛出但被吃掉的异常将被揭示。有两点可以解释为什么会这样。一,你只从调用线程中捕获远程异常,而你 .get() 结果。更详细地说:要使上述示例异步,只需将 .execute() 在代码中向上移动。您将发现远程异常的那一刻是在异步线程被炸毁并且您 .get() 结果之后。第二,通过捕获所有异常,您将捕获您可能构建的调用程序可能不知道的所有特殊和子类异常。

于 2013-02-14T10:35:02.640 回答