-2

Environment: OpenJDK12, JavaFX11

Context: I have a programm that reads a large document and keeps track of the bytes read by writing the amount to a json file. While reading the file, the ProgressBar updates showing a percentage. Not yet implemented, when the programm starts, the ProgressBar is set according to the bytes read of the file.

Issue: I read I can't pause a Task because it's the smallest unit of work, it's like one-shot process. So when I "pause" the Task with service.cancel(), the progress is still visible. When I want to resume the reading I have to set the Task to READY state with service.reset(), but with this the progress is lost.

How do I manage to not lose the progress?

Tried: I didn't try anything beacuse I don't know what the best approach is. I came up with 2 ideas:

  1. Instead of observe Task progress, observe the Service progress, (is it possible?). I mean, in the background, Service class binds its progress with the Task progress, they both implements Worker interface. So I thought to unbind Service from Task progress and implement its own updateProgress() method? Maybe it's just an overcomplicated idea.

  2. Inside createTask(), in task.setOnCancelled() get the progress properties, and set them somehow again to the ProgressBar.

In both cases I don't see how to bind the ProgressBar progress property with a Service whose Task has been cancelled.

Any help appreciated.

Relevant Code:

Controller:

public class MyController implements Initializable{
    @FXML
    private TableView<MyService> dataTable;

    @FXML
    private TableColumn<MyService, Double> progressColumn;
    @FXML
    private TableColumn<MyService,Object> actionColumn;

    private ObservableList<MyService> observableList;
    private List<MyService> serviceList;
    private MyThreadPoolExecutor threadPoolExecutor;  

    public MyController(){
      threadPoolExecutor = new MyThreadPoolExecutor();
      List<MyBean> myFileList ;//get files...

      myFileList.forEach(f->{
        serviceList.add(new MyService(f,threadPoolExecutor));
      });
      observableList = FXCollections.observableArrayList(serviceList);
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        progressColumn.setCellValueFactory(new PropertyValueFactory<MyService,Double>("progress"));
        progressColumn.setCellFactory(ProgressCell.<Double>forTableColumn());
        actionColumn.setCellValueFactory(new PropertyValueFactory<MyService,Object> "taskAction"));
        actionColumn.setCellFactory(ActionCell.<Object>forTableColumn(this));

        dataTable.setItems(observableList);
    }

    public void startReading(MyService service) {
      service.start();  
    }
    public void pauseReading(MyService service){
      service.cancel();
      service.reset();
    }

}

Progress cell:

  public class ProgressCell extends TableCell<MyService , Double> {

    private ProgressBar bar;
    private ObservableValue<Double> observable;


    public static <S>Callback<TableColumn<MyService ,Double>,TableCell<MyService ,Double>>
    forTableColumn() {
        return param -> new ProgressCell();
    }

    @Override
    protected void updateItem(Double item, boolean empty) {

        super.updateItem(item, empty);

        if (empty || item == null) {
            setGraphic(null);
        } else {
          TableColumn<MyService , Double> column = getTableColumn();
          observable = column == null ? null : column.getCellObservableValue(getIndex());

            if (observable != null) {
                bar.progressProperty().bind(observable);
            } else if (item != null) {
                bar.setProgress(item);
            }
            setGraphic(bar);
       }

    }

}

Service:

public class MyService extends Service<Double>{
    private SimpleObjectProperty<MyBean> file;
    private SimpleObjectProperty<Object> taskAction;

    public MyService(MyBean file, ThreadPoolExecutor executor) {
        this.file= new SimpleObjectProperty<MyBean>(file);
        super.setExecutor(executor);
    }

    @Override
    protected Task createTask() {
      try{
        MyTask task = new MyTask(this.file);
        task.setOnCancelled(event->{
          //get progress properties to save or something
        });
          return task;
      }catch(IOException e){
         e.printStacktrace();
      }
     return null;
    }

  //getters , setters & properties
}

ActionCell: has the buttons to play and pause (cancel) the Task

public ActionCell extends TableCell<MyService, Object>{
    private HBox hbox;
    private Button playButton;
    private Button pauseButton;

    public ActionCell(MyController cont){
      hbox = new HBox();
      playButton= new Button();
      pauseButton= new Button();

      playButton.setOnAction(event->{ 
            MyService service = getTableView().getItems().get(getIndex());
            cont.startReading(service);
            playButton.setDisable(true);
            pauseButton.setDisable(false);
        });

      pauseButton.setOnAction(event->{
            MyService service = getTableView().getItems().get(getIndex());
            cont.pauseReading(service); 
            playButton.setDisable(false);
            pauseButton.setDisable(true);
    });
    hbox.getChildren().addAll(playButton, pauseButton);

    }

    public static <S> Callback<TableColumn<MyService, Object>, TableCell<MyService, Object>>forTableColumn(MyController cont){
        return param -> new ActionCell(cont)
    }

    @Override
    protected void updateItem(Object item, boolean empty) {
       super.updateItem(item, empty);
    if (empty || item == null) {
        setGraphic(null);
    } else {            
        setGraphic(hbox);
    }
    }
} 
tec
  • 999
  • 3
  • 18
  • 40
  • you know the drill - [mcve] please :) Your base problem seems to be a "pauseable task", to solve that there's no need for custom cells .. – kleopatra Oct 01 '19 at 14:47
  • @kleopatra Can you please share a minimal reproducible example of "pauseable tasks"? – tec Oct 02 '19 at 06:45
  • @fabian May I ask what sql have to do in this question? The task does a simple process of check whether the file has been completely read or not. If yes, progress=100%, if not, compare read bytes with total and update progress. Downloading a file is reading bytes from a stream; reading local file is also read bytes from a stream. There will be not any misleading information as the programm will be the only able to access the json file. I will update with a minimal reproducible example as soon as I can. – tec Oct 02 '19 at 06:45
  • 2
    what? that's __your__ job ;) – kleopatra Oct 02 '19 at 09:02
  • @tomyforever Indeed I did not read the question thoroughly enough, but in general the issue is still the same: "Working with" the data may take much longer than reading it. (E.g. consider something like maven: parsing the pom file is pretty fast, but downloading dependencies can take quite some time; Also even the use of `BufferedReader` can screw up the measuring of progress.) Also note that communicating via a file is one of the least effective ways of passing data. Also you cannot be sure there is a mechanism in place preventing concurrent access to the file... – fabian Oct 02 '19 at 17:04

1 Answers1

-1

As I didn't found a useful design approach neither any pauseable Task examples having multiple Service and a ThreadExecutor, I found a solution myself. It's not perfect but either wrong.

The problem was in cancelling and resetting the Task when pressing pause. That cause the Task to not exist anymore, and the Worker state to change to READY and therefore lose the progress.

The solution was on reset() before start(), only if needed (if Task was cancelled). So when Task restarted would recover the progress itself and set it to the ProgressBar again.

public void startReading(MyService service) {
  //start task and reset only if needed
  if(service.getState().equals(Worker.State.CANCELLED)) {
     service.reset();
     service.start();
  }else {
     service.start();
  }

}

public void pauseReading(MyService service) {
    service.cancel();
}

References:

Run Service in background Thread

Service

Worker

Task

tec
  • 999
  • 3
  • 18
  • 40