I'm writing a program that creates a process using youtube-dl. That process has two InputStream
s (inputStream and errorStream) of which I want to reroute each into a text area.
I've been trying to get the TextArea
s 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);
}
}