4

如何在 JavaFX2 中的服务和 UI 之间通信用户定义的对象和用户定义的(检查的)异常?这些示例仅显示将字符串作为属性发送到服务,并将可观察字符串数组发送回 UI。

似乎只为简单类型定义了属性。StringProperty、IntegerProperty、DoubleProperty 等。目前我有一个用户定义的对象(不是简单类型),我希望 Task 对其进行操作并使用它生成的输出数据进行更新。我通过服务的构造函数发送它,它通过任务的构造函数传递它。我想知道参数必须通过属性传递的限制。此外,如果在 Task 的操作过程中抛出异常,它将如何从 Service 传递到 UI?我只看到一个 getException() 方法,没有传统的 throw/catch。

属性http://docs.oracle.com/javafx/2/binding/jfxpub-binding.htm

服务和任务http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm

服务 javadocs http://docs.oracle.com/javafx/2/api/javafx/concurrent/Service.html#getException ()

“因为任务设计用于 JavaFX GUI 应用程序,所以它确保对其公共属性的每次更改,以及状态、错误和事件处理程序的更改通知,都发生在 JavaFX 应用程序主线程上。访问这些属性从后台线程(包括 call() 方法)将导致引发运行时异常。

强烈鼓励所有任务都使用任务将运行的不可变状态进行初始化。这应该通过提供一个 Task 构造函数来完成,该构造函数采用执行任务所需的参数。不可变状态使得在任何线程中都可以轻松安全地使用,并确保存在多个线程时的正确性。”

但是如果我的 UI 只是在 Task 完成后才接触到对象,那应该没问题,对吧?

4

2 回答 2

6

服务有一个签名Service<V>,它<V>是一个通用类型参数,用于从服务提供的任务中指定返回对象的类型。

假设您要定义一个返回 Foo 类型的用户定义对象的服务,那么您可以这样做:

 class FooGenerator extends Service<Foo> {
   protected Task createTask() {
     return new Task<Foo>() {
       protected Foo call() throws Exception {
         return new Foo();
       }
     };
   }
 }

要使用该服务:

 FooGenerator fooGenerator = new FooGenerator();
 fooGenerator.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
   @Override public void handle(WorkerStateEvent t) {
     Foo myNewFoo = fooGenerator.getValue();
     System.out.println(myNewFoo);
   }
 });
 fooGenerator.start();

如果要在每次启动或重新启动服务之前将输入值传递给服务,则必须更加小心。您可以将要输入的值作为服务上的可设置成员添加到服务中。在调用服务的 start 方法之前,可以从 JavaFX 应用程序线程调用这些设置器。然后,当创建服务的任务时,将参数传递给服务的任务的构造函数。

这样做时,最好使所有信息在线程之间来回传递不可变。对于下面的示例,Foo 对象作为输入参数传递给服务,Foo 对象基于作为服务输出接收的输入。但是 Foo 本身的状态只在它的构造函数中初始化 - Foo 的实例是不可变的,一旦创建就不能改变,它的所有成员变量都是最终的,不能改变。这使得推理程序变得更加容易,因为您永远不必担心另一个线程可能会同时覆盖状态。这似乎有点复杂,但它确实让一切都非常安全。

class FooModifier extends Service<Foo> {
  private Foo foo;
  void setFoo(Foo foo) { this.foo = foo; }

  @Override protected Task createTask() {
    return new FooModifierTask(foo);
  }

  private class FooModifierTask extends Task<Foo> {
    final private Foo fooInput;
    FooModifierTask(Foo fooInput) { this.fooInput = fooInput; }

    @Override protected Foo call() throws Exception {
      Thread.currentThread().sleep(1000);
      return new Foo(fooInput);
    }
  }
}

class Foo {
  private final int answer;
  Foo()          { answer = random.nextInt(100); }
  Foo(Foo input) { answer = input.getAnswer() + 42; }
  public int getAnswer() { return answer; }
}

Service在 Service javadoc中还有一个向 a 提供输入的示例。


要从服务返回自定义用户异常,只需在服务的任务调用处理程序期间抛出用户异常。例如:

 class BadFooGenerator extends Service<Foo> {
   @Override protected Task createTask() {
     return new Task<Foo>() {
       @Override protected Foo call() throws Exception {
         Thread.currentThread().sleep(1000);
         throw new BadFooException();
       }
     };
   }
 }

可以像这样检索异常:

 BadFooGenerator badFooGenerator = new BadFooGenerator();
 badFooGenerator.setOnFailed(new EventHandler<WorkerStateEvent>() {
   @Override public void handle(WorkerStateEvent t) {
     Throwable ouch = badFooGenerator.getException();
     System.out.println(ouch.getClass().getName() + " -> " + ouch.getMessage());
   }
 });
 badFooGenerator.start();

我创建了几个可执行示例,您可以使用它来尝试一下。

于 2013-01-05T02:09:37.480 回答
2

似乎只为简单类型定义了属性。StringProperty、IntegerProperty、DoubleProperty 等。目前我有一个用户定义的对象(不是简单类型),我希望 Task 对其进行操作并使用它产生的输出数据进行更新

如果您想要一个可用于您自己的类的属性,请尝试 SimpleObjectProperty,其中 T 可以是 Exception,或者您需要的任何东西。

此外,如果在 Task 的操作过程中抛出异常,它将如何从 Service 传递到 UI?

您可以在 UI 中的 Task#onFailedProperty 上设置一个 EventHandler,其中包含有关失败时要做什么的逻辑。

但是如果我的 UI 只是在 Task 完成后才接触到对象,那应该没问题,对吧?

如果你从你的 UI 调用它,你肯定会在 javaFX 线程上,所以你会没事的。您可以通过调用 Platform.isFxApplicationThread() 断言您在 javaFX 线程上。

于 2013-01-05T00:09:17.617 回答