使用SwingWorker时如何处理异常?

24

我在Java 6中使用SwingWorker来避免在事件分派线程上运行长时间运行的代码。

如果在我的done()方法中调用get()返回一个异常,处理异常的适当方式是什么?

我特别担心可能出现的InterruptedException。JavaDoc示例只是忽略了异常,但多年来我已经学到,吞噬异常会导致难以调试的代码。

一个示例用法如下:

new SwingWorker<String, Void>() {

    @Override
    protected String doInBackground() throws Exception {
        // do long-running calculation
        return result;
    }

    @Override
    protected void done() {
        try {
            setTextField(get());
        } catch (InterruptedException e) {
            e.printStackTrace();  
        } catch (ExecutionException e) {
            e.printStackTrace();  
        }
    }
}.execute();
7个回答

12

这是一篇老帖子,但我想澄清一些内容:

SwingWorker.get会抛出InterruptedException和ExecutionException作为已检查异常。

另外它还会抛出一个特定的未检查异常CancellationException。当你调用了cancel方法后再试图调用get方法时,就会抛出该异常。虽然可能会抛出其他未检查异常,但CancellationException并不是一个“异常”或者意外的异常。

当doInBackground方法中产生异常时,ExecutedException异常就会被抛出,并且原始异常会被包装在ExecutionException中。当调用get()方法时,将会抛出ExecutionException。提取原始异常并进行管理的想法是好的(正如Emil H指出的那样)。

CancellationException是未检查异常,但我认为它应该是检查异常。API实现没有将其设置为检查异常的唯一借口就是它有一个status方法isCancelled()。你可以采取以下措施:
- 检测isCancelled()方法是否为真,如果是,则不要调用get()方法,因为这会抛出CancellationException。
- 使用try-catch包围get()方法并添加CancellationException,因为它是未检查异常,编译器不会强制要求添加。
- CancellationException未被检查,让你自由地忘记所有这些内容并得到一个惊喜。
- 不取消worker,则可以执行任何操作。

如果使用cancel(true)取消SwingThread,doInBackground中第一个可中断的方法调用(肯定是Thread.sleep,this.wait以及一些IO方法)将会抛出InterruptedException。但是这个异常不会被包装在ExecutionException中。 doInBackground会以中断异常结束。如果它被捕获并转换为其他异常,那么这些异常将被忽略,因为在此时cancel已经在EDT上调用了SwingThread.done,如果done调用了get,则只会得到标准的CancellationException,而不是InterruptedException!

如果使用cancel(false)取消,doInBackground内部不会引发InterruptException异常。如果使用cancel(true),但是doInBackground内没有可中断的方法调用,也不会引发异常。在这些情况下,doInBackground将按其自然循环执行。该循环应该测试isCancelled方法并正常退出。如果doInBackground未这样做,它将永远运行。

我尚未测试是否存在超时,但我不认为会发生。

对我来说,这仍然是一个灰色地带。 在什么情况下get会抛出InterruptedException?我想看一些简短的代码,因为我无法产生类似的异常。 :-)

附言 我在另一个问题&答案中记录了取消时done和state change监听器在doInBackground退出之前被调用的事实。 既然如此,这个-并不是一个错误-在设计doInBackground方法时需要特别注意。如果您对此感兴趣,请参见SwingWorker:何时调用done方法?


看起来这个灰色区域只是关于适当和正确的进程命名 +1。 - mKorbel

3
这很大程度上取决于后台作业可能导致的错误类型。如果doInBackground中的作业抛出异常,它将作为嵌套的ExecutionException传递到done方法中。在这种情况下,最好的做法是处理嵌套异常,而不是ExecutionException本身。
例如:如果工作线程抛出指示数据库连接已丢失的异常,您可能希望重新连接并重新启动作业。如果要完成的工作依赖于某种已经被使用的资源,最好提供重试或取消选择。如果抛出的异常对用户没有任何影响,只需记录错误并继续。
据我所记,我相信InterruptedException在这里不会成为问题,因为你在done方法中进行了get方法调用,因为InterruptedException只会在等待后台作业完成时中断get调用。如果发生意外事件,比如这样的情况,您可能希望显示错误消息并退出应用程序。

2
这不仅是一个接口问题,也是一个错误处理问题。许多应用程序添加了一些小表格,列出正在运行的后台作业。其中一个异常可能会闪烁产生错误的表格行,或者做一些像呈现警报这样具有破坏性的事情。这取决于异常的严重程度。我认为你可能需要回答的更难的问题是我们可能会遇到多少种潜在不同类型的异常,以及它们的相对严重程度是什么。
我认为一个简单的妥协可能是对最严重的错误提供模态警报,对其他任何错误,只需记录该事件,直到a)阻止用户继续操作或b)用户关闭文档/窗口,此时您可以显示同时发生的后台处理任务期间发生的异常列表,例如询问是否要保存任何未保存的缓冲区。

2
我建议的做法是让错误一直传递到操作开始的地方。
例如,如果用户点击一个按钮从数据源获取数据。如果出现问题,无论是凭据错误、网络错误、数据库错误还是其他任何错误,都不应该在工作线程中尝试解决它。
但是,如果你让它传播到任务开始的地方,那么你可以采取适当的错误纠正措施,例如再次弹出凭据对话框,显示“重试”对话框或甚至显示错误消息。

2
既然 SwingWorker 可能在后面抛出异常,那我该怎么做呢? 按钮调用的操作处理器已经返回了。 - Steve McLeod
1
我曾经遇到过异常从未被抛出的问题。我读到了这是由于SwingWorker内部捕获异常的方式所致,它可能会在稍后被抛出,也可能不会。为了解决这个问题,我通过在我的SwingWorker实现中设置一个异常字段,并在SwingWorker完成时检查它是否为空来解决这个问题。 - Jeremy Brooks

0
假设这是一个GUI应用程序,您可能希望在出现异常时提供有关失败操作的视觉反馈。

0
我应该澄清一下我的原始帖子。不要仅在done()方法中捕获特定的异常类型。在doInBackground()中执行的任何代码都可能抛出任何类型的异常,仅捕获你问题中的异常可能会导致在EDT(事件分派线程或主GUI线程)上抛出异常。在使用SwingWorkers时,在done()方法中捕获所有异常类型只是一个好习惯。
@Override
protected void done()
{
    try
    {
        if(!super.isCancelled())
        {
            super.get();
        }
    }
    catch(Exception ex)
    {
        ex.printStackTrace();
    }
}

-2

我猜您用 C# 得到的这些问题不多。 您需要理解异常并适当处理它(通常是将其放在堆栈更高的位置)。

InterruptedException - 当线程在等待时(大致上),被中断(通过Thread.interrupt)时抛出。为什么要中断线程?通常您希望线程停止正在做的事情-重置中断状态并退出。例如,如果 applet 线程在应该消失后继续运行很长时间,则 PlugIn 将中断 applet 线程。但是,在本例中,只要正确调用了 done,您根本不应该等待。因此,将异常包装在 IllegalStateException 中是适当的(API 文档可能应该说明这一点)。这真是个糟糕的 API。可能更有意义的是使用 publish/process 模式。

ExecutionException - 您需要处理包装的异常。如果您不期望特定类型的异常,请将其包装在未经检查的异常中。

通常,我建议清楚地区分在 EDT 上发生的事情和在 EDT 之外发生的事情。因此,在生产代码中避免使用 SwingWorker


1
为什么要避免在生产代码中使用SwingWorker?Sun公司推广它。http://java.sun.com/docs/books/tutorial/uiswing/concurrency/index.html - Eddie
避免使用 SwingWorker,因为它是可怕的设计。不要仅仅因为某个公司告诉你而使用某个东西。 - Tom Hawtin - tackline
4
Java 6 中引入的新框架 Tom 旨在增加现有框架的灵活性。例如,在 Java 6 中,当数据正在加载时,你实际上可以填充大量的数据到 JTable 中。尽管应该根据情况使用它,但建议避免使用有点牵强。你提出的替代方案都不能做到 Java 6 设计的功能。 - Jeach
1
您可以始终在后台检索数据(尽管在Java 1.1中没有EventQueue.invokeLater)。SwingWorker允许用很少的代码编写简单的示例。真实的代码往往会很快变得更加复杂。SwingWorker强制实现了一种糟糕的设计,使EDT和非EDT工作紧密耦合。 - Tom Hawtin - tackline
2
我认为你是错的。你确实想知道线程是否被打断。例如,后台任务被取消。因此,在调用done方法时,您将无法检索结果,而是会收到InterruptedException异常。 这不是一个非法状态。有人取消后台任务是合法的,你必须以感觉合适的方式处理它。我认为在InterruptedException的catch子句中,您应该编写处理代码,考虑到你无法检索到任务应该获取的数据。 - Andrei Vajna II
显示剩余2条评论

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