JavaFX的任务似乎会吞噬异常。这是一个错误还是一个特性?

23

考虑以下代码:

Thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e) -> {
    System.out.println("An exception occurred!");
});

// set the exception handler for the JavaFX application thread
Thread.currentThread().setUncaughtExceptionHandler((Thread t, Throwable e) -> {
    System.out.println("An exception occurred!");
});

Task<?> task = new Task() {
    @Override
    public Void call() throws Exception {
        throw new RuntimeException("foobar");
    };
};

new Thread(task).start();

如果我们运行代码,运行时异常不会触发默认的异常处理程序,而是被任务所消耗。我发现唯一对抗这种情况的方法是在task.setOnFailed中重新抛出异常:

task.setOnFailed((WorkerStateEvent t) -> {
    throw new RuntimeException(task.getException());
});

自从JavaFX 8开始支持UncaughtExceptionHandler,为什么异常没有传播到异常处理程序?
4个回答

11

Task.call()方法内,只需抛出异常并向任务添加一个如下的ChangeListener

task.exceptionProperty().addListener((observable, oldValue, newValue) ->  {
  if(newValue != null) {
    Exception ex = (Exception) newValue;
    ex.printStackTrace();
  }
});

在任务执行失败并抛出异常后,监听器会通知您哪个异常在执行期间被抛出。如果您处于JavaFX执行线程中,您可以轻松地将ex.printStackTrace();这一行替换为一个Alert


10

特性:Task 会自行维护一个 exception 属性。其思想是,当任务引发异常时,任务失败,并且可以查询引发了哪个异常。在这方面,Task 被设计为准批处理作业,在后台运行,可能会悄悄地失败。

这也反映了一些异步行为;异常的处理位置不是在调用 start 的地方。


1
有没有一种方法可以在不将异常包装在RuntimeException中的情况下重新抛出异常?我尝试过throw task.getException();但编译器只是报错。 - user555
据我所知,我对JavaFX的了解非常有限。 - Joop Eggen
2
不行。正如您在文档中所看到的那样(https://docs.oracle.com/javafx/2/api/javafx/event/EventHandler.html),Eventhandler 的 handle 方法不会抛出任何异常。您也不能将异常传递给周围的块进行重新抛出,因为由于异步调用,该块已经被离开。 - Franz Deschler

6
可能有点晚了,但您可以直接打印异常本身:
task.setOnFailed(new EventHandler<WorkerStateEvent>() {
    @Override
    public void handle(WorkerStateEvent arg0) {
        Throwable throwable = task.getException(); 
        throwable.printStackTrace();
    }
}

异常总是会被抛出,但您可以使用此方法将其显示给用户或记录下来。


2

我知道这个问题很老,但是我搜索了答案,关于这个主题并没有找到太多的信息。我认为如果您愿意,您可以重新抛出异常。以下是示例代码:

public class Main extends Application {

@Override
public void start(Stage stage) {

    Task<Void> task = new Task<Void>() {
        @Override
        protected Void call() throws Exception {
            throw new IndexOutOfBoundsException();
        }
    };
    task.setOnSucceeded(evt -> System.out.println("Task succeeded!"));
    task.setOnCancelled(evt -> System.out.println("Task cancelled!"));
    task.setOnFailed(evt -> {
        System.out.println("Task failed!");
        if (task.getException() instanceof IndexOutOfBoundsException) {
            System.out.println("...with an IndexOutOfBoundsException");
        } else if (task.getException() instanceof NumberFormatException) {
            System.out.println("...with a NumberFormatException");
        } else {
            System.out.println("...with another, unexpected execption");
        }
    });

    VBox box = new VBox();
    Scene scene = new Scene(box, 200, 200);
    stage.setScene(scene);
    stage.setTitle("Thread Example");
    stage.show();

    new Thread(task).start();

}

public static void main(String[] args) {
    launch(args);
}
}

控制台输出: 任务失败! ...出现IndexOutOfBoundsException异常

如果任务内部抛出异常,则该任务将进入“失败”状态。您可以在setOnFailed方法中处理任务的失败。这个方法中的所有代码都在JavaFX的应用程序线程中,但是您可以通过task.getException()来获取任务的异常。此外,这段代码仅适用于JavaFX(我尝试在普通Java应用程序中获得相同的输出,但未成功)。


1
没有好的解决方案,因为我必须“猜测”异常...好吧,如果你知道你的代码,你可能知道是什么异常,但并不总是这样。 - Ahmed Hasn.

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