【问题标题】:SwingWorker exceptions lost even when using wrapper classes即使使用包装类,SwingWorker 异常也会丢失
【发布时间】:2012-12-18 20:47:35
【问题描述】:

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

我收到的小程序需要向上传播异常。但我什至无法抓住它。我正在使用来自this blog entry 的 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 的特点是会自动调用真正的 SwingWorker 的done()/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();
    }
}

【问题讨论】:

  • 所有这些都是基于错误的假设。阅读javadoc for the get() method:如果后台计算抛出异常,它会抛出 ExecutionException。
  • 另见Q&A
  • @JBNizet 是的,这就是 SimpleSwingWorker 的 done() 调用 get()、捕获 ExecutionException 并将其作为新的 RuntimeException 重新抛出的原因。这不是重点吗?如果不是,那么我们就是在谈论彼此,你必须更加明确。
  • @TiStrga:原来的 SwingWorker 通过在重写的done() 方法中调用get() 来强制你处理异常,捕获ExecutionException,并处理这个异常。您的包装器没有提供任何处理异常的方法:要么存在异常,它被抛出但没有人可以捕获它并且包装器的 done() 方法永远不会被调用,或者没有异常并且包装器的 done() 方法叫做。 SwingWorker 不吃异常。你的包装器可以。
  • 我说的是您在问题中提出的 SimpleSwingWorker 类。 SwingWorker 设计得很好。我建议按照 javadoc 中的说明使用它。

标签: java swing swingworker


【解决方案1】:

忘记你的包装器,它会吃掉异常,而 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
        }
    }
}

【讨论】:

  • 我想这就是我感到沮丧的原因。 SimpleSwingWorker#done() 调用 get,执行 try-catch,并重新抛出异常。但你说它在吃它们,我不明白如何或为什么。我将此答案标记为正确答案,然后删除代码;可能有很多方法可以让它正确,但我的同事都不能告诉我为什么这段代码不起作用。
  • +1 for generics SwingWorker&lt;String, Object&gt;, SwingWorker is well designed. -100 :-),我认为不是,我不同意,我想念在将 SwingWorker 添加到官方 Suns Api 之前实现的方法,
  • @TiStrga:包装器会抛出它们,但除了 EDT 的默认异常处理程序之外,没有人可以捕获它们。所以基本上:你不能处理抛出的异常。
  • @JBNizet 我认为 done() 方法也在 EDT 上被调用,或者至少在父线程(在这个应用程序中应该是同一件事)和(未经检查的)异常从那里抛出的可能会被同一线程捕获。如果不是这样,那么我对 SwingWorker 的理解就太有缺陷了,无法修复。 :-\ 我已经替换了所有的实时代码;我们将直接管理线程。再次感谢您的耐心等待!
  • 在 EDT 上调用 done() 方法。这就是我在代码示例中所说的。但它不是由你调用的。它由 SwingWorker 调用,就像一个事件监听器:当计算完成时,在 EDT 上调用 done()。您必须覆盖 done() 并在那里获得任务的结果。如果后台计算任务抛出异常,则获取任务结果会抛出 ExecutionException。
【解决方案2】:

包装器似乎按预期工作。但是,如果发生异常,它的实现将永远不会调用done()。这不适用于许多情况。在done() 中调用get() 可能更简单。这将抛出 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();
            }
        });         
    }
}

【讨论】:

  • +1 表示invokeLater()。正如您所展示的,抛出一个显式异常会使效果更容易看到。
【解决方案3】:

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

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() 结果之后。第二,通过捕获所有异常,您将捕获您可能构建的调用程序可能不知道的所有特殊和子类异常。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-19
    • 2011-10-16
    • 1970-01-01
    • 2016-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-29
    相关资源
    最近更新 更多