What things need to keep in Mind while developing multithread Javafx application? - java

I'm new to Javafx and developing an IDE using this. The problem i'm facing with JavaFX is that i have to use Platform.runLater() to reflect changes in GUI from other threads. As my IDE i'm developing use multiple threads to keep up to date information and using Platform.runLater() makes application unresponsive. And sometime background processes has to print output of millions of line which i think cause problem when multiple threads try to do same. I tried to put a counter so that if output is larger than 250000 lines it will print output after 250000 lines else in other case it will print immediately after completion of the thread, even in this case if two or more thread try to execute Platform.runLater() (also there are other threads which creates tree with checkbox items and reflect realtime values) application hangs but everything in background is keep running normally and even application doesn't throw any exception. In normal java swing app i didn't face any similar problem. So i'm seeking guidance to tackle these problems. Can somebody gave me PRO tips to solve similar problems? :)
Edit On The request of #jewelsea
I tried to keep the sample code as simple as possible
FxUI.java
public class FxUI extends Application {
public static TextArea outputArea;
#Override
public void start(Stage primaryStage) {
outputArea= new TextArea();
Button btn = new Button();
btn.setText("Start Appending Text To Text Area");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Thread r=new Thread( new Runnable() {
#Override
public void run() {
for (int i = 0; i < 10; i++) {
Thread t= new Thread(new simpleThread(i));
t.start();
try {
Thread.sleep(1000);
System.out.println("Thread Awake");
} catch (InterruptedException ex) {
Logger.getLogger(FxUI.class.getName()).log(Level.SEVERE, null, ex);
}
} }
});
r.start();
}
});
VBox root = new VBox(30);
outputArea.setWrapText(true);
outputArea.setPrefHeight(400);
root.getChildren().add(outputArea);
root.getChildren().add(btn);
Scene scene = new Scene(root, 500, 500);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
simpleThread.java
public class simpleThread implements Runnable {
int threadnumber;
public simpleThread(int j) {
threadnumber = j;
}
#Override
public void run() {
String output = "";
String content;
int length;
final String finalcontent2;
final int finallength2;
for (long i = 0L; i <= 10000; i++) {
final String finalcontent;
final int finallength;
if (i % 1000 == 0) {
output += "\nThread number = " + threadnumber + " \t Loop Counter=" + i;
content = FxUI.outputArea.getText() + "\n" + output;
length = content.length();
finallength = length;
finalcontent = "" + content;
Platform.runLater(new Runnable() {
#Override
public void run() {
System.out.println("appending output");
FxUI.outputArea.setText(finalcontent);
FxUI.outputArea.positionCaret(finallength);
}
});
} else {
output += "\nThread number = " + threadnumber + " \t Loop Counter=" + i;
}
System.out.println("Thread number = " + threadnumber + " \t Loop Counter=" + i);
}
}
}

I think the first real issue here is that your code is simply massively inefficient. Building up a string in a loop is a really bad thing to do: you create a new object and copy all the characters every time. Additionally, each time you update the text area, you are copying the entire existing text, creating another String by concatenating the additional content, and then replacing all the existing content with the new content. The string concatenation is going to run in quadratic time (as you are increasing the length of the strings each time) and you're going to cause mayhem for Java's string interning process.
Also, note that you shouldn't read the state of a node in the scene graph anywhere except on the FX application thread, so your line
content = FxUI.outputArea.getText() + "\n" + output;
is not thread-safe.
In general, to build up a string in a loop, you should use a StringBuilder to build up the string contents. If you're using a TextArea, it has an appendText(...) method which is all you need to update it.
Update following discussion in comments:
Having made those general comments, making those improvements doesn't really get you to a state where the performance is acceptable. My observation there is that the TextArea is slow to respond to user input even after the threads have completed. The issue is (I guess) that you have a large amount of data which is actually associated with a "live" part of the scene graph.
A better option here is probably to use a virtualized control such as a ListView to display the data. These only have cells for the visible portion and reuse them as the user scrolls. Here is an example. I added selection and copy-to-clipboard functionality as that is the main thing you would miss going from a TextArea to a ListView. (Note that if you have a huge number of things selected, the String.join() method is very slow to run. You might have to create a background task for that and a blocking dialog to show its progress if that's important.)
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class BigListBackgroundThreadDemo extends Application {
private static final int NUM_ITERATIONS = 10_000 ;
private static final int NUM_THREADS_PER_CALL = 5 ;
#Override
public void start(Stage primaryStage) {
ListView<String> data = new ListView<>();
data.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
Button startButton = new Button("Start");
Button selectAllButton = new Button("Select All");
Button selectNoneButton = new Button("Clear Selection");
Button copyToClipboardButton = new Button("Copy to clipboard");
copyToClipboardButton.disableProperty().bind(Bindings.isEmpty(data.getSelectionModel().getSelectedItems()));
AtomicInteger threadCount = new AtomicInteger();
ExecutorService exec = Executors.newFixedThreadPool(5, r -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t ;
});
startButton.setOnAction(event -> {
exec.submit(() -> {
for (int i=0; i < NUM_THREADS_PER_CALL; i++) {
exec.submit(createTask(threadCount, data));
try {
Thread.sleep(500);
} catch (InterruptedException exc) {
throw new Error("Unexpected interruption", exc);
}
}
});
});
selectAllButton.setOnAction(event -> {
data.getSelectionModel().selectAll();
data.requestFocus();
});
selectNoneButton.setOnAction(event -> {
data.getSelectionModel().clearSelection();
data.requestFocus();
});
copyToClipboardButton.setOnAction(event -> {
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putString(String.join("\n", data.getSelectionModel().getSelectedItems()));
Clipboard.getSystemClipboard().setContent(clipboardContent);
});
HBox controls = new HBox(5, startButton, selectAllButton, selectNoneButton, copyToClipboardButton);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(5));
BorderPane root = new BorderPane(data, null, null, controls, null);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private Task<Void> createTask(AtomicInteger threadCount, ListView<String> target) {
return new Task<Void>() {
#Override
public Void call() throws Exception {
int count = threadCount.incrementAndGet();
AtomicBoolean pending = new AtomicBoolean(false);
BlockingQueue<String> messages = new LinkedBlockingQueue<>();
for (int i=0; i < NUM_ITERATIONS; i++) {
messages.add("Thread number: "+count + "\tLoop counter: "+i);
if (pending.compareAndSet(false, true)) {
Platform.runLater(() -> {
pending.set(false);
messages.drainTo(target.getItems());
target.scrollTo(target.getItems().size()-1);
});
}
}
return null ;
}
};
}
public static void main(String[] args) {
launch(args);
}
}

In JavaFX you have to do your background process in a Service which run a Task . By doing this you'll won't freez your GUI thread
Quick example, if you want a String as a return value of your process.
The service :
public class MyService extends Service<String> {
#Override
protected Task<String> createTask() {
return new Task<String>() {
#Override
protected String call() throws Exception {
//Do your heavy stuff
return "";
}
};
}
}
Place you want to use your service :
final MyService service = new MyService();
service.setOnSucceeded(e -> {
//your service finish with no problems
service.getValue(); //get the return value of your service
});
service.setOnFailed(e -> {
//your service failed
});
service.restart();
You have other method like setOnFailed, for the different status. So implement what you need.
You can also monitor this service, But I let you read the doc for this. It's quit simple.
You should also read JavaFX concurency

Related

JavaFX Simple Update Label (Threading)

I'm trying to demonstrate to a few beginner programmers how to set a label on a JavaFX app to auto update. Basically they would like the value to decrease every minute or so on the label without any user interaction.
Java isn't my strong point and looking through some previous questions I get that I need to deal with threads and Runnable().
I have put the code together below that works, but I was just wondering if there is a better way of doing this or an easier way to demonstrate the same outcome with simpler code.
public class MainTimer2 extends Application {
private int count = 100;
private Label response = new Label(Integer.toString(count));
public static void main(String[] args) {
launch(args);
}
//Update function
private void decrementCount() {
count--;
response.setText(Integer.toString(count));
}
#Override
public void start(Stage myStage) {
myStage.setTitle("Update Demo");
//Vertical and horizontal gaps set to 10px
FlowPane rootNode = new FlowPane(10, 10);
rootNode.setAlignment(Pos.CENTER);
Scene myScene = new Scene(rootNode, 200, 100);
myStage.setScene(myScene);
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
Runnable updater = new Runnable() {
#Override
public void run() {
decrementCount();
}
};
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
System.out.println("Timer error");
}
// UI update is run on the Application thread
Platform.runLater(updater);
}
}
});
// don't let thread prevent JVM shutdown
thread.setDaemon(true);
thread.start();
rootNode.getChildren().addAll(response);
myStage.show();
}
}
Count down by using PauseTransition:
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class MainTimer2 extends Application {
private int count = 100;
private Label response = new Label(Integer.toString(count));
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage myStage) {
myStage.setTitle("Update Demo");
//Vertical and horizontal gaps set to 10px
FlowPane rootNode = new FlowPane(10, 10);
rootNode.setAlignment(Pos.CENTER);
Scene myScene = new Scene(rootNode, 200, 100);
myStage.setScene(myScene);
rootNode.getChildren().addAll(response);
myStage.show();
update();
}
private void update() {
PauseTransition pause = new PauseTransition(Duration.seconds(1));
pause.setOnFinished(event ->{
decrementCount();
pause.play();
});
pause.play();
}
//Update function
private void decrementCount() {
count = (count > 0) ? count -1 : 100;
response.setText(Integer.toString(count));
}
}
Alternatively you could use Timeline:
private void update() {
KeyFrame keyFrame = new KeyFrame(
Duration.seconds(1),
event -> {
decrementCount();
}
);
Timeline timeline = new Timeline();
timeline.setCycleCount(Animation.INDEFINITE);
//if you want to limit the number of cycles use
//timeline.setCycleCount(100);
timeline.getKeyFrames().add(keyFrame);
timeline.play();
}

JavaFX update text from Task

I want to change text I create a task and increment i, but I want to set a new text on this same place when i is changed, but old text doesn't disappear. It's my code. On swing I will be use repaint()
Task task = new Task<Void>() {
#Override
public Void call() throws Exception {
int i = 0;
while (true) {
final int finalI = i;
Platform.runLater(new Runnable() {
#Override
public void run() {
String a = "aaa";
if(finalI>4){
a = "sadsa";
}
if(finalI>10){
a = "sadsadsadsadsad";
}
gc.fillText(a, 150, 250+10);
}
});
i++;
Thread.sleep(1000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
As I mentioned in my comment, the problem is that Canvas really acts like a drawing board. You have drawn some text on it then you have drawn another text without erasing the previous text.
In your case, when you want to store a reference to the text to be able to update it, it is more reasonable to use a Pane and put a Text instance on it.
I have created an example for you:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
Pane pane = new Pane();
Text text = new Text("");
pane.getChildren().add(text);
Task<Void> task = new Task<Void>() {
String a = "Initial text";
#Override
public Void call() throws Exception {
int i = 0;
while (true) {
if (i > 4)
a = "I is bigger than 4";
if (i > 10)
a = "I is bigger than 10";
Platform.runLater(() -> {
text.setText(a);
// If you want to you can also move the text here
text.relocate(10, 10);
});
i++;
Thread.sleep(1000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
root.setCenter(pane);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Note: You can also eliminate the Platform.runlater(...) block by updating the messageProperty of the task inside call() then binding the textProperty of the Text to this property.
Example:
Pane pane = new Pane();
Text text = new Text("");
text.relocate(10, 10);
pane.getChildren().add(text);
Task<Void> task = new Task<Void>() {
{
updateMessage("Initial text");
}
#Override
public Void call() throws Exception {
int i = 0;
while (true) {
if (i > 4)
updateMessage("I is bigger than 4");
if (i > 10)
updateMessage("I is bigger than 10");
i++;
Thread.sleep(1000);
}
}
};
text.textProperty().bind(task.messageProperty());
Thread th = new Thread(task);
th.setDaemon(true);
th.start();

Synchronizing a sequence of asynchronous calls

I'm using JavaFX's WebView to parse a website. The site contains a bunch of links - I need to open each of them separately, in a given order, and retrieve one information from each of them.
In order to make sure that WebView has loaded the whole site, I'm listening to changed event of WebEngine and waiting for newState == Worker.State.SUCCEEDED. The problem is that this call is asynchronous. When I'm calling webEngine.load(firstAddress);, the code immediately returns and before this page will have been loaded, my code will call another webEngine.load(secondAddress);, and so on.
I understand why it's done this way (why async is better than sync), but I'm a beginner in Java and I'm not sure what's the best solution to this problem. I somehow understand multithreading and stuff, so I've already tried a semaphore (CountDownLatch class). But the code hangs on await and I'm not sure what I'm doing wrong.
Could someone please show me how it should be done the right way? Maybe some universal pattern how to cope with scenarios like this?
A pseudocode of what I want to achieve:
WebEngine webEngine = new WebEngine();
webEngine.loadPage("http://www.something.com/list-of-cars");
webEngine.waitForThePageToLoad(); // I need an equivalent of this. In the real code, this is done asynchronously as a callback
// ... some HTML parsing or DOM traversing ...
List<String> allCarsOnTheWebsite = webEngine.getDocument()....getChildNodes()...;
// allCarsOnTheWebsite contains URLs to the pages I want to analyze
for (String url : allCarsOnTheWebsite)
{
webEngine.loadPage(url);
webEngine.waitForThePageToLoad(); // same as in line 3
String someDataImInterestedIn = webEngine.getDocument()....getChildNodes()...Value();
System.out.println(url + " : " + someDataImInterestedIn);
}
System.out.println("Done, all cars have been analyzed");
You should use listeners which get invoked when the page is loaded, instead of blocking until it's done.
Something like:
WebEngine webEngine = new WebEngine();
ChangeListener<State> initialListener = new ChangeListener<State>() {
#Override
public void changed(ObservableValue<? extends State> obs, State oldState, State newState) {
if (newState == State.SUCCEEDED) {
webEngine.getLoadWorker().stateProperty().removeListener(this);
List<String> allCarsOnTheWebsite = webEngine.getDocument()... ;
loadPagesConsecutively(allCarsOnTheWebsite, webEngine);
}
}
};
webEngine.getLoadWorker().addListener(initialListener);
webEngine.loadPage("http://www.something.com/list-of-cars");
// ...
private void loadPagesConsecutively(List<String> pages, WebEngine webEngine) {
LinkedList<String> pageStack = new LinkedList<>(pages);
ChangeListener<State> nextPageListener = new ChangeListener<State>() {
#Override
public void changed(ObservableValue<? extends State> obs, State oldState, State newState) {
if (newState == State.SUCCEEDED ) {
// process current page data
// ...
if (pageStack.isEmpty()) {
webEngine.getLoadWorker().stateProperty().removeListener(this);
} else {
// load next page:
webEngine.load(pageStack.pop());
}
}
}
};
webEngine.getLoadWorker().stateProperty().addListener(nextPageListener);
// load first page (assumes pages is not empty):
webEngine.load(pageStack.pop());
}
If you want to run all the tasks concurrently, but process them in the order they were submitted, have a look at the following example:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ProcessTaskResultsSequentially extends Application {
#Override
public void start(Stage primaryStage) {
ListView<String> results = new ListView<>();
List<Task<Integer>> taskList = new ArrayList<>();
for (int i = 1; i<= 10 ; i++) {
taskList.add(new SimpleTask(i));
}
ExecutorService exec = Executors.newCachedThreadPool(r -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t ;
});
Thread processThread = new Thread(() -> {
for (Task<Integer> task : taskList) {
try {
int result = task.get();
Platform.runLater(() -> {
results.getItems().add("Result: "+result);
});
} catch (Exception e) {
e.printStackTrace();
}
}
});
processThread.setDaemon(true);
processThread.start();
taskList.forEach(exec::submit);
primaryStage.setScene(new Scene(new BorderPane(results), 250, 400));
primaryStage.show();
}
public static class SimpleTask extends Task<Integer> {
private final int index ;
private final static Random rng = new Random();
public SimpleTask(int index) {
this.index = index ;
}
#Override
public Integer call() throws Exception {
System.out.println("Task "+index+" called");
Thread.sleep(rng.nextInt(1000)+1000);
System.out.println("Task "+index+" finished");
return index ;
}
}
public static void main(String[] args) {
launch(args);
}
}

How to correctly use ExecutorService to manage the number of concurrently running SwingWorkers?

I am generating SwingWorkers based on a number of connections I need to make. I am trying to make it so that I set a fixed number of maximum concurrant SwingWorkers and when one of those finishes another one is started (or many others are started if many have finished). Based on http://java.dzone.com/articles/multi-threading-java-swing I am setting up the basic SwingWorker like this:
SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
#Override
protected Boolean doInBackground() throws Exception {
System.out.println("One SwingWorker just ran! ");
}
return true;
}
// Can safely update the GUI from this method.
protected void done() {
boolean status;
try {
// Retrieve the return value of doInBackground.
status = get();
statusLabel.setText("Completed with status: " + status);
} catch (InterruptedException e) {
// This is thrown if the thread's interrupted.
} catch (ExecutionException e) {
// This is thrown if we throw an exception
// from doInBackground.
}
}
};
worker.execute();
Now I'm uncertain in how to implement the mechanism I described above.
From https://stackoverflow.com/a/8356896/988591 I saw that I can use an ExecutorService to execute instances of SwingWorker and that this interface also allows to set the number of threads:
int n = 20; // Maximum number of threads
ExecutorService threadPool = Executors.newFixedThreadPool(n);
SwingWorker w; //don*t forget to initialize
threadPool.submit(w);
I think this is what I need but I don't know how to put the whole thing together (..I am also quite new to Java..). Could someone guide me a bit in the process of implementing this? Say at the top I have int totalTask = 100; Maybe it's just a matter of some loops but I can't seem to find any really easy-to-follow examples around and I just can't totally wrap my mind around it yet so.. I would appreciate some help! Thanks.
UPDATE: I have set up the ExecutorService this way:
ExecutorService executorService = Executors.newFixedThreadPool(500);
for (int i = 0; i < 20 ; i++) {
executorService.submit(worker);
//I tried both...
//executorService.execute(worker);
}
and I have removed worker.execute() called after the SwingWorker above but the output from console is just a single "One SwingWorker just ran!" line, how is that ? What did I do wrong?
You'd do something like this:
Initiate the executorservice using a fixed threadPool as you have
shown.
In a loop create your runnable. As many runnables as you need.
You can have 50 threads and 5000 runnables. After the 1st 50
runnables, whichever thread is free will pick up the 51st task, and
so on.
Call the executorservice's execute method with your Runnable.
Once all are done, you shutdown the executor service.
Like this:
ExecutorService executorService = Executors.newFixedThreadPool(500);
for (long i = 0; i < 1000000; i++) {
Runnable populator = new YourRunnable();
executorService.execute(populator);
}
executorService.shutdown();
while(!executorService.isTerminated()){
}
That isTerminated can be used to check whether the executorServices is actually down. Since you can have several executor threads running even after you call the shutdown() method (because they haven't completed the task yet), that while loop acts like a wait call.
And one key thing: whatever you want to pass to the ExecutorService, it must be a Runnable implementation. In your case, your SwingWorker must be a Runnable.
That's interesting. With an ExecutorService with a limited pool, you just need to submit the workers whenever you want and only that amount of workers will be executed concurrently at the same time.
I made this little test app that where you can press the buttons to submit some workers, as fast as you want, and you can see how the amount of workers executing at any given time is never higher than the value numberOfThreads that you initialized the ExecutorService with.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class SwingWorkerExecutorTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new SwingWorkerExecutorTest();
}
});
}
public SwingWorkerExecutorTest()
{
JFrame frame = new JFrame("Frame");
int numberOfThreads = 2; //1 so they are executed one after the other.
final ExecutorService threadPool = Executors.newFixedThreadPool(numberOfThreads);
JButton button1 = new JButton("Submit SwingWorker 1");
button1.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
String workerName = "Worker 1";
appendMessage("Submited " + workerName);
SwingWorker worker = new TestWorker(workerName);
threadPool.submit(worker);
}
});
JButton button2 = new JButton("Submit SwingWorker 2");
button2.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
String workerName = "Worker 2";
appendMessage("Submited " + workerName);
SwingWorker worker = new TestWorker(workerName);
threadPool.submit(worker);
}
});
JButton button3 = new JButton("Submit SwingWorker 3");
button3.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
String workerName = "Worker 3";
appendMessage("Submited " + workerName);
SwingWorker worker = new TestWorker(workerName);
threadPool.submit(worker);
}
});
JPanel buttonsPanel = new JPanel();
buttonsPanel.add(button1);
buttonsPanel.add(button2);
buttonsPanel.add(button3);
frame.add(buttonsPanel, BorderLayout.PAGE_END);
_textArea = new JTextArea("Submit some workers:\n");
_textArea.setEditable(false);
frame.add(new JScrollPane(_textArea));
frame.setSize(600, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private class TestWorker extends SwingWorker
{
public TestWorker(String name)
{
_name = name;
}
#Override
protected Object doInBackground() throws Exception
{
String message = "A " + _name + " has started!";
appendMessage(message);
doHardWork();
return null;
}
#Override
protected void done()
{
String message = "A " + _name + " has finished!";
appendMessage(message);
}
private void doHardWork()
{
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
private String _name;
}
private static void appendMessage(String message)
{
_textArea.append(message + "\n");
System.out.println(message);
}
private static JTextArea _textArea;
}
It looks like this:
For example, with a number of threads of 2 you'll se how if you submit a lot of workers it takes 2 at a time and executes them.
Please take a look at the source code of SwingWorker.
You can something similar to execute() method
public final void execute() {
getWorkersExecutorService().execute(this);
}
At this point you can create your one ExecutorService and manage the pool
SwingWorker<Boolean, Void> test = new SwingWorker<Boolean, Void>() {
private ExecutorService service = new ThreadPoolExecutor(5, 10,
10L, TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(),
new ThreadFactory() {
AtomicInteger count= new AtomicInteger();
#Override
public Thread newThread(Runnable r) {
return new Thread("Pooled SwingWorker " + count.getAndAdd(1));
}
});
#Override
protected Boolean doInBackground() throws Exception {
return true;
}
public void doIt() {
service.execute(this);
}
};
That moment when you think: It was so obvious!
ExecutorService executorService = Executors.newFixedThreadPool(20);
for (int i = 0; i < 500; i++) {
SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
#Override
protected Boolean doInBackground() throws Exception {
System.out.println("One SwingWorker just ran!");
return true;
}
protected void done() {
boolean status;
try {
status = get();
} catch (InterruptedException e) {
// This is thrown if the thread's interrupted.
} catch (ExecutionException e) {
// This is thrown if we throw an exception
// from doInBackground.
}
}
};
executorService.submit(worker);
}
It works great!

Progress bar in Swing (Java) for command tools

I have several C/C++ command line tools that I'm wrapping with Java.Swing as GUI. The command line tools can take minutes to hours. Progress bar seems like a good idea to keep users sane. I'm also thinking it might be nice to wrap a GUI for the progress bar, instead of just using system out. But how?
I'm thinking the command line tools can write percents to stderr and I can somehow read it in java. Not exactly sure what the mechanics for this would be. I'm also not clear on asynchronous display (learned a bit about invokeLater() ). New to Java, and would appreciate general suggestions as well. Thanks.
--- update ---
Thanks everyone for your suggestions. Here's the resulting code.
private void redirectSystemStreams() {
OutputStream out_stderr = new OutputStream() {
#Override
public void write(final int b) throws IOException {
update(String.valueOf((char) b));
}
#Override
public void write(byte[] b, int off, int len) throws IOException {
update(new String(b, off, len));
}
#Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
};
System.setErr(new PrintStream(out_stderr, true));
}
private void update(final String inputText) {
int value = 20; //parse inputText; make sure your executable calls fflush(stderr) after each fprintf().
jProgressBar.setValue(value);
/* Also one can redirect to a textpane
SwingUtilities.invokeLater(new Runnable() {
public void run() {
//update jTextPane with inputText
}
});
*/
}
That's seems very fragile, better would be to communicate via sockets in a well established protocol or with some sort of RCP ( perhaps Google's protobuf ) or even webservices.
If you still insists you can launch a process in Java with ProcessBuilder that will give you a Process reference of which you can get the InputStream to read the standard output, but again, that seems very fragile to me.
I hope this helps.
For the progress bar part of your problem you can do something like the following. Note that this is just an example to illustrate the point.
Basically, a thread is created to do the work. Presumably this Runner thread will be interacting with your C/C++ code to get its progress. It then calls update on the Progress Bars Dialog class.
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
public class Main {
private int value;
private Progress pbar;
public static void main(String args[]) {
new Main();
}
public Main() {
pbar = new Progress();
Thread t = new Thread(new Runner());
t.start();
}
class Progress extends JDialog {
JProgressBar pb;
JLabel label;
public Progress() {
super((JFrame) null, "Task In Progress");
pb = new JProgressBar(0, 100);
pb.setPreferredSize(new Dimension(175, 20));
pb.setString("Working");
pb.setStringPainted(true);
pb.setValue(0);
label = new JLabel("Progress: ");
JPanel panel = new JPanel();
panel.add(label);
panel.add(pb);
add(panel, BorderLayout.CENTER);
pack();
setVisible(true);
}
public void update(){
pb.setValue(value);
if(value >= 100){
this.setVisible(false);
this.dispose();
}
}
}
class Runner implements Runnable {
public void run() {
for (int i = 0; i <= 100; i++) {
value++;
pbar.update();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
}
}
}
// Create a window
JFrame frame = new JFrame("Progress");
// Creates a progress bar and add it to the window
JProgressBar prog = new JProgressBar();
frame.add(prog);
// Run C/C++ application
try {
Process p = Runtime.getRuntime().exec(new String[]{"filename","arg1","arg2","..."});
// Get InputStream
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
// Update the progress when recieving output from C/C++
new java.util.Timer().schedule(new TimerTask(){
public void run(){
String str = "";
while ((str=br.readLine()!=null) {
prog.setValue(new Integer(str)); // Set Value of Progress Bar
prog.setString(str+"%"); // Set Value to display (in text) on Progress Bar
}
}
},0,100); // Check every 100 milliseconds
// Fit the window to its contents and display it
frame.pack();
frame.setVisible(true);
} catch (Exception e) {
System.out.println("Failed To Launch Program or Failed To Get Input Stream");
}

Categories