1

I am working on the design of a multi-threading app in Javafx and would like to have a TableView with columns for Name and Progress of each Thread. After doing much research I found a similar example of what I am trying to accomplish here:

JavaFX Update progressbar in tableview from Task

(Which points to this: 'https://community.oracle.com/message/10999916')

The problem I am running into, however, is illustrated well in this example; how can you call a 'Task' object multiple times to update a ProgressIndicator?

My understanding from Oracle's documentation is that a Task object "is a one-shot class and cannot be reused". It would seem then that one can only invoke the call() method of a Task object once. I need to update the Task multiple times as it progresses through a Thread class, not call it once and arbitrarily increment through a For loop.

I have read about binding to Listeners and creating Service classes, but I am unsure if those are actual resolutions to this problem. I would therefore like to ask if this is even possible in Javafx, or if perhaps I am overlooking something. In the event someone has accomplished this in the past, it would be tremendously helpful if you might be able to illustrate how through the example provided previously.

Any direction on this would be appreciated, thank you.

-Drew

EDIT 1: I edited my wording as it was inaccurate.

EDIT 2: Here is an example with some pseudo code. Say I had a class with the following code:

public static class TaskEx extends Task<Void>{

  @Override
  protected Void call(){

    updateProgress(.5, 1);

  return null

}

public static void callThread() {
  TableView<TaskEx> table = new TableView<TaskEx>();
  //Some code for data in table.

  TableColumn progressColumn = new TableColumn ("Progress");
  progressColumn.setCellValueFactory(new PropertyValueFactor("progress");

  table.setItems(<data>);
  table.getColumns();addAll(progressColumn);

  ExecutorService executor = Executors.newFixedThreadPool(<SomeNumber>);

  for(TaskEx task : table.getItems(){
    Threading.ThreadClass newThread = new Threading.ThreadClass(task);
    executor.submit(newThread, <uniqueID>);
  }
}

Then say I had a second class for Threading with this logic:

static class ThreadClass extends Thread{
  Task progressTask;

  public ThreadClass(Task task, Integer id){
    progressTask = task;
  }

public void run(){
  ExecutorService executor =  Executors.newFixedThreadPool(<someNumber>);

  //This invokes the Task call for the correct progressIndicator in the Tableview.
  //It will correctly set the progressIndicator to 50% done.
  executor.submit(progressTask);

  /* Main logic of the Threading class that involves the 'id' passed in. */

  //This will do nothing because you cannot invoke the Task call more than once.
  executor.submit(progressTask);
  }
}

That is the sort of workflow I need, but I'm unsure how to accomplish this.

Community
  • 1
  • 1
Drew B
  • 465
  • 3
  • 6
  • 18
  • 2
    *"It would seem then that one can only call 'updateProgress' on a Task object once."* This is clearly not true: the example you linked calls `updateProgress(...)` in a loop, so each task calls it multiple times. All the documentation means is that you can only invoke a `Task`'s `call()` method once. You can call `updateProgress` as many times as you like from within the `call()` method. The example you linked appears to be a complete implementation of what you describe you want to do. Can you explain what is different about your question? – James_D May 09 '17 at 00:27
  • 2
    Hey James. I'm sure I worded that poorly. What I mean is, in order to increment the progress of a Task properly, I would need to invoke the Task's call() method multiple times. I know I can loop through and keep calling updateProgress once I'm in the call() method, but I don't know how long a thread will take to finish when I call it. I had planned to invoke the Task's call() method once when it was at a certain point in the Threads logic, then call it a second time when it was further down, and so on. – Drew B May 09 '17 at 00:35
  • 1
    I think you must have misunderstood the purpose of a `Task`? The task represents the "thing you are doing" - so it should be the progress of the task in which you are interested. If that's not the case, what are you using the `Task` for? Can you post some code to illustrate the problem? – James_D May 09 '17 at 00:54
  • In other words, the `Task` is the `Runnable` that the thread executes - the `call()` method is essentially the `run()` method for that `Runnable`. The `Task` is the thing that the thread is doing. – James_D May 09 '17 at 01:00
  • 1
    From the way I see, you have probably done it wrongly. From what I see, you probably spawned X threads (not using `Task`) doing X different things concurrently. Then each of the spawned thread creates its `Task` and updates progress back to UI thread (to the individual `ProgressBar`). If this is what you are doing, then your `Task` is redundent - you might as well have a reference of the respective `ProgressBar` and just use `Platform.runLater(() -> progressBar.setProgress(x));` to do it. Alternatively, instead of `new Thread()`, you should turn them into `Task`. – Jai May 09 '17 at 01:11
  • I have updated my question to try and explain what I'm trying to accomplish and what I'm running into. I believe I cannot do a simple 'setProgress' as the progressIndicators are part of a TableView and need a CellFactory to update. – Drew B May 09 '17 at 03:35

1 Answers1

1

It seems like you don't get what we were talking about. You are trying to do your logic in the Thread.run(), and then each thread is creating a Task just to do the update of progress.

What you need is really to shift your logic from Thread.run() to Task.call(). Your thread is really just a thread, and all it does is to run a Runnable object (which is the Task).

public class TaskEx extends Task<Void> {
    @Override
    protected Void call() {
        // Do whatever you need this thread to do 
        updateProgress(0.5, 1);

        // Do the rest
        updateProgress(1, 1);
    }
}


public static void callThread() {
    TableView<TaskEx> table = new TableView<TaskEx>();
    ObservableList<TaskEx> data = FXCollections.observableArrayList<>();
    data.add(new TaskEx()); // Add the data you need

    TableColumn progressColumn = new TableColumn("Progress");
    progressColumn.setCellValueFactory(new PropertyValueFactory("progress"));
    progressColumn.setCellFactory(column -> {
        return new TableCell<TaskEx, Double> {
            private final ProgressBar bp = new ProgressBar();

            @Override
            public void updateItem(Double item, boolean empty) {
                super.updateItem(item, empty);

                if (empty || item == null) {
                    setText(null);
                    setGraphic(null);
                }
                else {
                    bp.setProgress(item.doubleValue());
                    setGraphic(bp);
                }
            }
        }
    });

    table.setItems(data);
    table.getColumns().add(progressColumn);

    ExecutorService executor = Executors.newFixedThreadPool(data.size());

    for (TaskEx task : table.getItems()) {
        executor.submit(task);
    }
}

This implement removes ThreadClass because there should not be any logic that must be done at a thread sub-class. If you really need to access the thread object as part of your logic, call Thread.getCurrentThread() from your TaskEx.call().

This implement also opens multiple threads doing exactly the same thing (which is quite meaningless). If you need to do a set of different logics, you can either make a set of different Task subclasses, or add a constructor taking in Runnable objects in TaskEx.

E.g.

public class TaskEx extends Task<Void> {
    private final Runnable[] logics;

    public TaskEx(Runnable[] logics) {
        this.logics = logics;
    }

    @Override
    protected Void call() {
        for (int i = 0; i < logics.length; i++) {
            logics[i].run();
            updateProgress(i, logics.length);
        }
    }
}
Jai
  • 8,165
  • 2
  • 21
  • 52
  • Thank you Jai! You and James_D helped me better understand 'Task' and its purposes, though I admittedly have a long way to go :) – Drew B May 10 '17 at 23:03
  • @DrewB Learning is an ongoing process. I have received lots of guidance (directly and indirectly) from experts like James_D, jewelsea and DVarga :) – Jai May 11 '17 at 01:20