2

I'm writing a program that creates a process using youtube-dl. That process has two InputStreams (inputStream and errorStream) of which I want to reroute each into a text area.

I've been trying to get the TextAreas to update without locking the JavaFX thread. It's working but I feel like it's terribly inefficient as it creates a large number of Task objects that only append a line. I've recreated the code I've been using below, using List<String> instead of BufferedReader to simplify the problem a bit.

When I press the button it will create two threads, one for each list, with an UpdateTask. The UpdateTask then creates a WriteTask gives it to Platform.runLater() which places it on the JavaFX thread again.

Surely there must be a better way to do this?

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;

public class ConcurrentTest extends VBox{

    TextArea output;
    TextArea error;
    Button start;
    TextField writable;

    public ConcurrentTest(){
        // Init components
        output = new TextArea();
        output.setEditable(false);
        error = new TextArea();
        error.setEditable(false);
        // Init button
        start = new Button("Print stuff");
        start.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent arg0) {
                List<String> outputLines = Arrays.asList("A", "B", "C", "D");
                List<String> errorLines = Arrays.asList("A", "B", "C", "D");;

                Thread outputThread = new Thread(new UpdateTask<String>(output, outputLines));
                outputThread.setDaemon(true);
                outputThread.start();

                Thread errorThread = new Thread(new UpdateTask<String>(error, errorLines));
                errorThread.setDaemon(true);
                errorThread.start();
            }
        });
        writable = new TextField();
        writable.setPromptText("Write while some text areas are getting updated.");

        // Add components
        this.getChildren().addAll(output, error, start, writable);
    }

    // UPDATE TASK CLASS
    public class UpdateTask<V> extends Task<V>{

        TextArea target;
        Iterator<String> it;

        public UpdateTask(TextArea target, List<String> lines){
            this.target = target;
            it = lines.iterator();
        }

        @Override
        protected V call() throws Exception {
            while(it.hasNext()){
                Thread.sleep(1500);     // Time to type something to test
                Platform.runLater(new WriteTask<String>(target, it.next()));
            }
            return null;
        }

    }

    // WRITE TASK CLASS
    public class WriteTask<V> extends Task<V>{

        TextArea target;
        String line;

        public WriteTask(TextArea target, String line) {
            this.target = target;
            this.line = line;
        }

        @Override
        protected V call() throws Exception {
            target.appendText(line + "\n");
            return null;
        }
    }
}

For the entire program, the launcher with main:

import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;

public class ConcurrentTestLauncher extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            Parent root = new ConcurrentTest();
            Scene scene = new Scene(root);
            primaryStage.setTitle("Concurrent FX Test");
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
Kevin T.
  • 21
  • 4
  • 1
    The solution over here should work 100% the same: http://stackoverflow.com/questions/27832347/javafx-swingworker-equivalent/27835238#27835238 – eckig Jan 12 '15 at 09:59
  • `Task`s are relatively lightweight; you only want to avoid creating many `Thread`s. You are only creating a new thread on each button click, which should be just fine. If you see performance problems, you could consider creating a single [`ExecutorService` of some particular type](http://docs.oracle.com/javase/8/docs/api/index.html?java/util/concurrent/Executors.html) and submitting the `UpdateTask`s to it; but I don't think there will be an issue here. – James_D Jan 12 '15 at 12:12
  • @James_D I thought calling `Platform.runLater()` with a lot of `Task`s would be inefficient, I'll just keep using this system then. Thanks for the comment! – Kevin T. Jan 12 '15 at 12:19
  • You're throttling them at one every 1.5 seconds, so that's fine. If you repeatedly called `Platform.runLater(...)` without blocking, you would flood the FX Application Thread and make the UI unresponsive, but (as far as I can see) you're not doing that here. – James_D Jan 12 '15 at 12:42
  • @James_D Would calling it 15 times per second be too much? So in my example I'd have 2 threads each making 15 `runLater()` calls for a total of 30 calls / second. How would I go about checking performance? I'm not quite familiar with multithreading. – Kevin T. Jan 12 '15 at 14:30

0 Answers0