简体   繁体   中英

GUI is blocked when using a while loop to check when task is complete

I have this piece of code here, essentially what this is trying to do is when the user presses the "Listen" Button this code is run.

In the for loop, we get one name (for example "John"), play the audio recording of "John" through a bash command; wait until the name has finished playing (the while loop) then continue to get the next name and play that (and so forth). However the while loop I believe is blocking the GUI in javafx, what is a better solution to this?

Another thing I'd like to do is while the thread has started, I want to check if the "Listen" button is pressed again (if it is, I want to stop the thread/task).

How do I do this?

for (String s : _userDatabase.getSelectedNames()) {
                        Task<Integer> task = new Task<Integer>() {
                            try {
                                    Process pb = new ProcessBuilder("bash", "-c", //SomeCommand).start();
                                    return pb.waitFor();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                    return 1;
                                }
                        };

                        Thread playAudio = new Thread(task);
                        playAudio.setDaemon(true);
                        playAudio.start();
                        while(!task.isDone()){

                        }
                    }

There is no reason to busy wait for a Task to complete. A Task provides many ways to register callbacks for when it reaches a certain State . One of these mechanisms is using EventHandler s and the various properties (eg onSucceeded , onFailed , etc...). You could also listen to any of the appropriate Worker properties (eg state , value , exception , etc...).

Task<?> task = ...;
task.setOnSucceeded(event -> {
    // Do what needs to be done...
});
task.setOnFailed(event -> {
    // Do what needs to be done...
});
// execute task on background thread

The event handlers and/or property listeners will always be called on the JavaFX Application Thread .

Since you are creating an anonymous class, you could also override the protected methods of Task ; these methods are also called on the JavaFX Application Thread .

Task<Integer> task = new Task<>() {

    @Override
    protected Integer call() throws Exception {
        // background work...
    }

    @Override
    protected void succeeded() {
        // do what needs to be done
    }

    @Override
    protected void failed() {
        // do what needs to be done
    }

}

Note: If you override these methods in a ScheduledService make sure to call the super implementations as necessary. See the Javadoc for more info.


There are a couple of ways you can launch the next Task only once the previous one has completed. One is to use a single thread to launch all the Task s and queue them up.

ExecutorService executor = Executors.newSingleThreadExecutor();
for (String s : _userDatabase.getSelectedNames()) {
    Task<Integer> task = ...;
    // add callbacks to task if necessary
    executor.execute(task);
}
executor.shutdown();

Another option is to use an Iterator . One example:

private void execute(final List<String> names, final Executor executor, 
                     final Consumer<Task<Integer>> onNewTask) {
  Objects.requireNonNull(names);
  Objects.requireNonNull(executor);
  Objects.requireNonNull(onNewTask);

  if (names.isEmpty()) {
    return;
  }

  final Iterator<String> iterator = names.iterator();

  class TaskImpl extends Task<Integer> {

    private final String name;

    private TaskImpl(String name) {
      this.name = name;
    }

    @Override
    protected Integer call() throws Exception {
      // background work...
    }

    @Override protected void succeeded() { complete(); }
    @Override protected void failed() { complete(); }
    // @Override protected void cancelled() { complete(); }

    private void complete() {
      if (iterator.hasNext()) {
        TaskImpl task = new TaskImpl(iterator.next());
        onNewTask.accept(task);
        executor.execute(task);
      }
    }

  }

  // launch first task
  TaskImpl firstTask = new TaskImpl(iterator.next());
  onNewTask.accept(firstTask);
  executor.execute(firstTask);
}

A third option is to do all the work in a single Task . Basically, put the for loop inside the call() method.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM