3 Replies Latest reply: Aug 19, 2013 11:36 AM by csh RSS

    Displaying running background threads on the UI

    csh

      Hi,

       

      sometimes I have the situation, that a user action needs some time to perform. So I put that work in a Task or Service.

       

      I want to inform the user, that his or her action is still being performed. A simple ProgressIndicator is enough.

       

      I know how to do it with a single task, but theoretically there could be many tasks running, even some which were not triggered by user action.

       

      So I want a similar thing as in Eclipse or IntelliJ: A "background tasks" control, which displays all running threads.

       

      Is there an easy way to do this, I mean from the Thread/Task/Service perspective, not the UI.

       

      Do I need to register each thread I start in that control, e.g. when it is scheduled?

       

      Should I derive Task to do this logic?

       

      Is there a way to "watch" all threads?

       

      What is the best approach?

       

       

      My idea was to add each Task to the control. And the control listens to the running property. When it has finished it is removed from the controls observable list.

      But what about simple Threads, which might be started somewhere outside the JavaFX Application Thread?

        • 1. Re: Displaying running background threads on the UI
          James_D

          For the more general part of the question (Threads which may be started outside of the JavaFX Application Thread):

           

          I've never worked with this part of the Thread API, but Threads belong to ThreadGroups. You can retrieve the group to which a Thread belongs with Thread.getThreadGroup(). ThreadGroups are hierarchical; every ThreadGroup except the top level ThreadGroup has a parent ThreadGroup (ThreadGroup.getParent()) and you can get references to all the child ThreadGroups with ThreadGroup.enumerate(ThreadGroup[]). Thread also has a getState() method which returns a Thread.State enum instance.

           

          Of course, none of this is observable in the JavaFX properties sense, so you would likely have to periodically poll this rather than be able to passively listen for state changes.

           

          For the easier part, some ideas:

           

          I have done one thing which is conceptually similar to what you're trying to do. I have one client-server type application in which the data model communicates with a Remote EJB service layer. All calls to the service layer are threaded. The model exposes an IntegerProperty representing the number of Tasks which are pending (either waiting to be executed or in execution). Most calls to the model result in the creation of a Task whose call() method executes on the remote service. The pertinent parts of the model look like this:

           

          public class Model {
            private final ExecutorService executorService ;
            private final IntegerProperty pendingTasks ;
            // ...
            public Model() throws NamingException {
              // ...
            executorService = Executors.newSingleThreadExecutor() ;
            pendingTasks = new SimpleIntegerProperty(0);
          }
          
            private void addListenersToTask(final Task<?> task) {
            task.stateProperty().addListener(new ChangeListener<State>() {
            @Override
            public void changed(ObservableValue<? extends State> observable,
            State oldValue, State newValue) {
            State state = observable.getValue();
            if (state == State.SUCCEEDED) {
            decrementPendingTasks();
            task.stateProperty().removeListener(this);
            } else if (state == State.FAILED) {
          task.stateProperty().removeListener(this);
            try {
            context.close();
            } catch (NamingException exc) {
            System.out.println("Warning: could not close context");
            }
            status.set(ConnectionStatus.DISCONNECTED);
            }
            }
            });
            }
          
            private void scheduleTask(Task<?> task) {
            addListenersToTask(task);
            incrementPendingTasks();
            executorService.submit(task);
            }
          
            private void decrementPendingTasks() {
            if (! Platform.isFxApplicationThread()) {
            throw new IllegalStateException("Must be on FX Application thread");
            }
            if (pendingTasks.get()<=0) {
            throw new IllegalStateException("Trying to decrement non-positive number of pending tasks");
            }
            pendingTasks.set(pendingTasks.get()-1);
            }
          
            private void incrementPendingTasks() {
            if (! Platform.isFxApplicationThread()) {
            throw new IllegalStateException("Must be on FX Application thread");
            }
            pendingTasks.set(pendingTasks.get()+1);
            }
          
            public int getNumberOfPendingTasks() {
            return pendingTasks.get() ;
            }
          
            public ReadOnlyIntegerProperty numberOfPendingTasksProperty() {
            return pendingTasks ;
            }
          
            // ...
          }
          
          

           

          All tasks are scheduled for execution by calling the scheduleTask(...) method above, which increments the pendingTasks property and adds a listener to decrement it when it completes. The only way for Tasks to fail in this application is if the remote service fails (no other exceptions are thrown by the tasks), which is why I close the naming context on failure, and there's currently no functionality to cancel the tasks. The code will need modifying to deal with cancelation or failure if this is not the case.

           

          Obviously you could do more than just a simple counter here using similar techniques. For example, you could keep an ObservableList<Task> for the tasks, add a task to that list when it is submitted, and remove it when it completes. Then you could provide that list to a control which could display the state of each task.

           

          Another option might be to write a wrapper for ExecutorService which implemented this functionality, rather than implementing it in your model. (I can elaborate a bit if this is not clear, but I haven't actually tried this myself.) If you somehow exposed this ExecutorService wrapper to the whole application and used it for all your threading (whether invoked from the FXApplication Thread or not) then you could at least be aware of those threads, and should be able to monitor them relatively easily. Any threads which were not executed through your ExecutorService wrapper would still be difficult to monitor.

           

          Not sure how much help that is.

          • 3. Re: Displaying running background threads on the UI
            csh

            Thanks for your answers. I think James_D really got my point and suggested the same solution as I had in my mind.

             

            The wrapper class which holds an ExecutorService, where you can submit tasks sounds good.

             

            jsmith's link is also very revealing.

             

            Unfortunately I haven't yet worked much with ExecutorService.