This
article isn’t about concurrency or threads synchronizing, neither about
responsive design. It is about making your Java application user responsible
and friendly.
All that threads
Working
with threads in Java is … well I can’t say easy, but at least well documented
and great supported.
You can
create your own Thread object and pass it the Runnable object with logic, which
should be executed in background. You can use SwingWorker API, which is great
for long running tasks with callbacks to GUI (for example to update progressbar).
There is also ExecutorService (part of java.util.cuncurent API), which solves
asynchronous issues and starting in Java 7 there is the Fork/Join framework to
add even more support for parallel programming (but it solves different
problem, than described in this article).
Working
with threads has its own quirks, but when using threads together with GUI, we
always have to keep in mind two major rules:
- Time–consuming tasks shouldn’t be run on Event Dispatch Thread (just a few seconds running task should be considered and treated as time-consuming)
- Swing components should ALWAYS be accessed from EDT (no exceptions).
More on
this can be found here: javadoc
It seems,
that everybody knows these rules, but you can still find in many Java applications
code, that computes ultimate question of life, the universe and everything or
calls some web service directly from the Event Dispatch Thread. Why is that
done?
Well, because it is easy
Calling
some long-running computation or service in EDT is so tempting because it works
:-), it’s easy :-| and it block’s the Event Dispatch Thread :-( And the
blocking of EDT is the reason why you can find it literally everywhere – I click
the “Compute” button, start the computation and I want to wait for the result,
before I do something else (fill the table with results for example). This
works simply because I am using one thread for everything and this thread is
busy doing my long-running task. But there are drawbacks…
Drawbacks
Drawbacks
of using EDT to run long tasks don’t have to be always seen, until your tasks
are really long. How long? It depends. There was a rule, that any action longer
than 15 seconds should notify user of its results, via some progressbar, or
just say that something is happening. But Moore’s law is still valid and computers
are faster every year and users are more and more demanding and the
psychological barrier of 15 seconds was long time ago pushed to one or two
seconds.
Users just
don’t want to wait, and if so, they want to know what is happening and when it
will end, or even more they want to have choice to cancel the operation.
So the user is waiting, what is so bad about it?
The bad
thing is that the application doesn’t respond. Because the EDT is doing your
computation, or waiting for some response, it can’t process input events
(mouse, keyboard) or even paint. Your progressbar doesn’t move, it maybe doesn’t
event paint. And even worse – the same happen to all application frame. You can
see it, when you run such application, start some long task, minimize it and
try to restore it back. All you see now is only black rectangle. This is
starting to be bad, doesn’t?
Example of
application, running computation on EDT:
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
public class BusyApp {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
JProgressBar progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
panel.add(progressBar);
frame.setTitle("Busy application demo");
frame.setContentPane(panel);
frame.setSize(300, 80);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
System.out.println("Start of execution");
long result = 0;
for (long i = 0; i < 100000000l; i++) {
Float f = (float) (Math.sin(i) * i); //doing some computation to slow it down
result = i + result;
}
System.out.println("End of execution");
}
});
}
}
Is there a better way?
There is always
some better way, but there is also its price. You have to change your thinking
and not just block your main thread, but create background action and use a callback
to invoke your reaction (filling the table with some data from our first
example).
Luckily in
Java exists something called ExecutorService. I won’t waste your time and space
here to describe all details of ExecutorService, now we can settle for a rudimentary
simplification, that it can run some task in background and let you wait for
the result, while the GUI is walking and talking :-) (For more details, look at
the javadoc).
Example of
ExecutorService usage:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Long> future = executorService.submit(new Callable<Long>() {
public Long call() throws Exception {
System.out.println("Start of execution");
long result = 0;
for (long i = 0; i < 50000000l; i++) {
Float f = (float) (Math.sin(i) * i); //doing some computation to slow it down
result = i + result;
}
System.out.println("End of execution");
return result;
}
});
JFrame frame = new JFrame();
JPanel panel = new JPanel();
JProgressBar progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
panel.add(progressBar);
frame.setTitle("Executor service demo");
frame.setContentPane(panel);
frame.setSize(300, 80);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
System.out.println("we are waiting for result, but swing is still painting");
Long result = future.get();
System.out.println("completed, result: " + result);
Showing user some progress
For now we
leave threads behind and focus on user interface and letting user know, that
something is happening.
The most
common indication is progressbar and modern progressbar is something that
rotates, so we will create rotating progressbar showing above the application.
In combination with consuming mouse and keyboard events, this is used to block
user screen and show, what is happening.
Example of
JBusyComponent usage:
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import org.divxdede.swing.busy.JBusyComponent;
public class BusyComponentDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
JTree tree = new JTree();
panel.add(tree);
JBusyComponent<JPanel> busyComponent = new JBusyComponent<JPanel>(panel);
busyComponent.setBusy(true);
frame.setTitle("Busy application demo");
frame.setContentPane(busyComponent);
frame.setSize(300, 80);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
In this example we use JBusyComponent
as a modern progressbar (see JBusyComponent). The component is set as a contentPane of the main
application frame and when set to busy it draws rotating icon over the frame,
leaving the content visible and blocks all user input.
Now we can combine it together:
We have demonstrated how to run task in a background thread and wait for some
result and how to notify user about running action. Now it’s time to combine
all together and create user responsive application.
Example of responsive application:
Looking much better now, right? Computation is running in background, code is waiting for the result of computation and GUI is live and responsive.
JFrame frame = new JFrame();
JPanel panel = new JPanel();
JTree tree = new JTree();
panel.add(tree);
JBusyComponent<JPanel> busyComponent = new JBusyComponent<JPanel>(panel);
frame.setTitle("Executor service demo");
frame.setContentPane(busyComponent);
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
ExecutorService executorService = Executors.newSingleThreadExecutor();
System.out.println("Setting busy");
busyComponent.setBusy(true);
Future<Long> future = executorService.submit(new Callable<Long>() {
public Long call() throws Exception {
System.out.println("Start of execution");
long result = 0;
for (long i = 0; i < 10000000l; i++) {
Float f = (float) (Math.sin(i) * i); //doing some computation to slow it down
result = i + result;
}
System.out.println("End of execution");
return result;
}
});
System.out.println("We are waiting for result, but swing is still painting");
Long result = future.get();
System.out.println("Completed, result: " + result);
System.out.println("Not busy anymore");
busyComponent.setBusy(false);
Looking much better now, right? Computation is running in background, code is waiting for the result of computation and GUI is live and responsive.
Conclusion
We created application, that
runs some long task and while the task is being computed, user can see nice
progress visualization and with some additional configuration can even cancel
the running task.
And the post thought: please
do not run your time-consuming tasks on the EDT, it is killing your application’s
user experience.