任务:当任务完成时如何通知"完成"

时间:2018-04-05 11:03:08

标签: javafx binding task

...这是在它的所有属性 - 包括它的值 - 之后更新了吗?

用例是一个

的任务
  • "收集"将项目放入ObservableList中,这是调用方法的结果
  • 当任务是"完成"时,列表应设置为值,无论是正常还是取消

Task实现的片段(最后的完整示例):

@Override 
protected ObservableList<Rectangle> call() throws Exception {
    ObservableList<Rectangle> results = FXCollections.observableArrayList();
    for (int i=0; i<=count; i++) {
        // do fill list
        //... 

        try {
            Thread.sleep(200);
        } catch (InterruptedException interrupted) {
            if (isCancelled()) {
                // do update value on cancelled
                updateValue(results);
                break;
            }
        }
    }
    return results;
}

它的用途:

  • 将tableView的itemsProperty绑定到valueProperty
  • 取消&#34;完成&#34;

我的方法是听取其状态属性并解除状态变为已成功或取消。前者工作得很好,后者没有,因为在收到取消时,该值尚未更新,因此未设置项目。

// working ... but when to unbind?
table.itemsProperty().bind(task.valueProperty());

task.stateProperty().addListener((src, ov, nv) -> {
    if (Worker.State.SUCCEEDED == nv ) {
        // this is fine because implementation in TaskCallable first 
        // updates the value (with the result it got from T call())
        // then updates state
        LOG.info("succeeded" + task.getValue());
         table.itemsProperty().unbind();
    } else if (Worker.State.CANCELLED == nv) {
        LOG.info("receiving cancelled " + task.getValue());
        // can't unbind here, value not yet updated
        //   table.itemsProperty().unbind();
    } 
});

因此,如果取消,这将留下我仍然绑定的属性或空表。感觉就像我做错了什么。或核心任务impl没有预期的那么有用?这意味着由于无法安全清理,我们无法绑定到value属性(也没有任何其他类似的进程)(这里使用表项只是一个例子,因为它很容易看,所有类型的属性相同)。

问题是,如何正确地做/克服限制?

完整的例子:

public class TaskValueBinding extends Application {

    private Parent createListPane() {
        Task<ObservableList<Rectangle>> task = createListTask();
        Thread thread = new Thread(task);
        thread.setDaemon(true);

        TableView<Rectangle> table = new TableView<>();
        TableColumn<Rectangle, Double> xCol = new TableColumn<>("X");
        xCol.setCellValueFactory(new PropertyValueFactory<>("x"));
        TableColumn<Rectangle, Double> yCol = new TableColumn<>("Y");
        yCol.setCellValueFactory(new PropertyValueFactory<>("y"));
        table.getColumns().addAll(xCol, yCol);

        // working ... but when to unbind?
        table.itemsProperty().bind(task.valueProperty());

        task.stateProperty().addListener((src, ov, nv) -> {
            if (Worker.State.SUCCEEDED == nv ) {
                // this is fine because implementation in TaskCallable first 
                // updates the value (with the result it got from T call())
                // then updates state
                LOG.info("succeeded" + task.getValue());
                 table.itemsProperty().unbind();
            } else if (Worker.State.CANCELLED == nv) {
                LOG.info("receiving cancelled " + task.getValue());
                // can't unbind here, value not yet updated
                //   table.itemsProperty().unbind();
            } 
        });

        Label messageLabel = new Label("Message: ");
        Label message = new Label();
        message.textProperty().bind(task.messageProperty());

        Label progressAsText = new Label();
        Label progressLabel = new Label("Progress: ");
        progressAsText.textProperty().bind(task.progressProperty().asString());

        ProgressBar progress = new ProgressBar();
        progress.progressProperty().bind(task.progressProperty());

        Button start = new Button("Start");
        start.setOnAction(e -> {
            start.setDisable(true);
            thread.start();
        });
        Button cancel = new Button("Cancel");
        cancel.setOnAction(e -> task.cancel());
        cancel.disableProperty().bind(task.runningProperty().not());

        int row = 0;
        GridPane grid = new GridPane();
        grid.add(table, 0, row++, 20, 1);
        grid.add(messageLabel, 0, row);
        grid.add(message, 1, row++);
        grid.add(progressLabel, 0, row);
        grid.add(progressAsText, 1, row++);
        grid.add(progress, 0, row++, 2, 1);
        grid.add(start, 0, row);
        grid.add(cancel, 1, row++);
        return grid;
   }

    private Task<ObservableList<Rectangle>> createListTask() {
        Task<ObservableList<Rectangle>> task = new Task<ObservableList<Rectangle>>() {
            @Override 
            protected ObservableList<Rectangle> call() throws Exception {
                updateMessage("Creating Rectangles ...");
                ObservableList<Rectangle> results = FXCollections.observableArrayList();
                String message = "finished";
                int count = 10;
                for (int i=0; i<=count; i++) {
                    if (isCancelled()) {
                        updateValue(results);
                        // when do we get here?
                        message = "cancelled";
                        break;
                    }
                    Rectangle r = new Rectangle(10, 10);
                    r.setX(10 * i);
                    results.add(r);
                    updateProgress(i, count);
                    // Now block the thread for a short time, but be sure
                    // to check the interrupted exception for cancellation!
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException interrupted) {
                        if (isCancelled()) {
                            updateValue(results);
                            message = "interrupted";
                            break;
                        }
                    }
                }
                updateMessage(message);
                return results;
            }

        };
        return task;
    }


    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createListPane()));
        stage.setTitle(FXUtils.version());
        stage.show();
    }

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

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(TaskValueBinding.class.getName());

}

1 个答案:

答案 0 :(得分:1)

取消任务会立即触发state属性的更新。如果从应用程序中取消线程Platfrom.runLater未用于此目的,但cancel方法的调用会立即更新状态。这导致状态在任何updateValue调用使用value更新Platform.runLater属性之前发生更改。

Task并非旨在允许部分结果,因此您需要实现自定义逻辑以适应此目的。根据您的需要,当任务以任何方式完成时,您可以将Task子类化为触发自定义事件。

public abstract class PartialResultTask<T> extends Task<T> {

    // handler triggered after last change of value
    private Runnable onDone;

    public Runnable getOnDone() {
        return onDone;
    }

    public void setOnDone(Runnable onDone) {
        this.onDone = onDone;
    }

    protected abstract T calculateResult() throws Exception;

    private void onDone() {
        if (onDone != null) {
            Platform.runLater(onDone);
        }
    }

    @Override
    protected final T call() throws Exception {
        try {
            T result = calculateResult();
            updateValue(result); // update value to the final value
            onDone();
            return result;
        } catch (Exception ex) {
            onDone();
            throw ex;
        }
    }

}
private PartialResultTask<ObservableList<Rectangle>> createListTask() {
    PartialResultTask<ObservableList<Rectangle>> task = new PartialResultTask<ObservableList<Rectangle>>() {

        @Override
        protected ObservableList<Rectangle> calculateResult() throws Exception {updateMessage("Creating Rectangles ...");
            ObservableList<Rectangle> results = FXCollections.observableArrayList();
            int count = 10;
            for (int i = 0; !isCancelled() && i <= count; i++) {
                Rectangle r = new Rectangle(10, 10);
                r.setX(10 * i);
                results.add(r);
                updateProgress(i, count);
                // Now block the thread for a short time, but be sure
                // to check the interrupted exception for cancellation!
                try {
                    Thread.sleep(200);
                } catch (InterruptedException interrupted) {
                }
            }
            updateMessage(isCancelled() ? "canceled" : "finished");
            return results;
        }

    };
    return task;
}
task.setOnDone(() -> {
    table.itemsProperty().unbind();
});

task.stateProperty().addListener((src, ov, nv) -> {
    if (Worker.State.SUCCEEDED == nv) {
        // this is fine because implementation in TaskCallable first 
        // updates the value (with the result it got from T call())
        // then updates state
        LOG.info("succeeded" + task.getValue());
    } else if (Worker.State.CANCELLED == nv) {
        LOG.info("receiving cancelled " + task.getValue());
    }
});