Skip to main content

Swing threading model

17 replies [Last post]
armhold
Offline
Joined: 2003-12-25
Points: 0

The single thread model for Swing really places a burden on developers, and even when we manage to figure out how to make it work we end up with some very messy code.

You can't block the event dispatch thread, so you fork a new thread to deal with a slow task... but then your slow task needs to update the UI occasionally, so you have to deal with SwingUtilities.invokeLater(), or in some cases invokeAndWait. But invokeAndWait can cause a deadlock, so
you've gotta be careful with that.

Yes I know about SwingWorker- it's an OK solution for the simple case. But in any moderate to complicated Swing app it's very easy to get lost jumping into and out of the event dispatch thread.

Can we find some way to fix this? The modal dialog seems to do it- it blocks the calling thread, yet allows the GUI to remain responsive. I believe this is what the Foxtrot folks are using. The threading issue is a major hurdle to good, responsive GUI app development, and a standard solution is really needed.

Thanks for listening.

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
delvento
Offline
Joined: 2005-11-21
Points: 0

Hi.

> Your example starts new SwingWorker on every mouse
> event. All of them are executed and display result.
> Because there are so many SwingWorkers submitted for
> execution it takes so much time to finally see the
> result. (By the way the result one finally see on the
> screen is not necessarily produced by the last
> SwingWorker. There is no guarantee that SwingWorkers
> finish in the same order they are executed.)
Yes, I was aware of that. In other situations this may be what one want.

> Moving mouse obsoletes all the previously running
> background tasks. For this case one needs only last
> SwingWorker to finish and all others can be canceled
> as soon as the new one starts. In fact one needs no
> more than one SwingWorker running at all.
Mmmm this is very easy and it is good for a lot of events. Unfortunately, for this case, I think it is not appropriate: you have to stop moving your mouse to see something.
I would like a solution than cancels only the queue, not the current executing event. Say a method like
swingworker.cancelQueueAndExecute()
If you agree, I would like to submit my code that will illustrate better what I have in mind.

In such a case, where should I submit? Here
http://bugs.sun.com/services/bugreport/fix_details.jsp
or here https://swingworker.dev.java.net/

>
> to post code with indentation one can wrap code
> into [b][[/b]code[b]] [[/b]/code[b]][/b] brackets.
>

Thank you for the hint: it is not perfect, but it is much better than an unindented code.

Bye,
;Davide

idk
Offline
Joined: 2005-01-12
Points: 0

Hi,

>Mmmm this is very easy and it is good for a lot of events. Unfortunately, for this case, I think it is not appropriate: you have to stop moving your mouse to see something.
>I would like a solution than cancels only the queue, not the current executing event. Say a method like swingworker.cancelQueueAndExecute()

Understood. One can have a custom ExecutorService with API to purge the queue.
For your example ThreadPoolExecutor should do. It has remove(Runnable) method which removes the task from the executor's internal queue.
[code]
NEW_THREAD {
void doSomething(final MouseEvent e, final JTextComponent myTxt) {
if (worker != null) {
executor.remove(worker);
}
worker = new SwingWorker() {
public String doInBackground() throws Exception {
sleep();
return "well done!";
}

protected void done() {
try {
get(); /* check exceptions */
myTxt.setText(String.valueOf(e.getX()));
} catch (Exception ignore) {
}

}
};
executor.execute(worker);
}

public String getDescription() {
return "Nice, but a the new thread "
+ "will execute ALL the events.";
}
};
private static SwingWorker worker = null;
private static ThreadPoolExecutor executor =
(ThreadPoolExecutor) Executors.newFixedThreadPool(3);
[/code]
>If you agree, I would like to submit my code that will illustrate better what I have in mind.

Sure. Could you send it to idk AT dev.java.net ?

Thanks,
Igor

tsinger
Offline
Joined: 2003-06-10
Points: 0

> From where I am standing the concept of one thread
> that manages swing objects is just fine; it really
> is a good concept!

Completely agree. Why make it much more complex than necessary. Own frameworks arise automatically - like your UICompiler project.

dog
Offline
Joined: 2003-08-22
Points: 0

How about something like this (simplified):

you define your app-level commands w/some interface

interface ICommand {
void run(IAnswer a);
}

And your command replies to Swing via IAnswer:

interface IAnswer {

....public void reportProgress(float percent);

....public void end(Object result);
}

To run commands you use a dispatcher..

class Dispatcher {
....public void dispatch(ICommand c, IAnswer a) {
........Thread t = new Thread(
............new CmdWrapper(c, new AnswerWrapper(a)));
........t.start();
....}

....private static class CmdWrapper implements Runnable {
........public CmdWrapper(ICommand c, IAnswer a) {
............_cmd = c; _answer = a;
........}
........public void run() {
............_cmd.run(_answer);
........}
....}

....private static class AnswerWrapper implements IAnswer {
........public AnswerWrapper(IAnswer a) {
............_answer = a;
........}
........public void reportProgress(float percent) {
............SwingUtilities.invokeLater(new Runnable() {
................public void run() {
...................._answer.reportProgress(porcentaje);
................}
............});
........}
........public void end(Object result) {
............SwingUtilities.invokeLater(new Runnable() {
................public void run() {
...................._answer.end(result);
................}
............});
........}
....}
}

Now in your UI code just run something like:

public MyActionListener implements ActionListener {
..public void actionPerformed(ActionEvent e) {
....dispatcher.dispatch(factory.get(e.getActionCommand()),
......new AnswerAdaptor() {
........public void end(Object result) {
............// update UI
........}
....});
..}
}

Advantages:

- All commands run on their own thread.. everything is super responsive
- All answers run on Swing thread
- ICommand w/ a different dispatcher works on web apps too so you don't have to change any model code
- One mechanism.. very little specialized code

Caveats:

- Your model code should be thread safe (probably not so bad in a GUI app)

dog
Offline
Joined: 2003-08-22
Points: 0

Alternatively, if you don't want to bother to make the model threadsafe.. just run a single background thread and queue commands in the dispatcher.

delvento
Offline
Joined: 2005-11-21
Points: 0

Hello all.

> Yes I know about SwingWorker- it's an OK solution for
> the simple case. But in any moderate to complicated
> Swing app it's very easy to get lost jumping into and
> out of the event dispatch thread.

I am glad to see that in jdk1.6 javax.swing.SwingWorker was introduced.

I'm new here, so I'm still a little bit confused about projects and I don't know if
this (I mean: https://swingworker.dev.java.net/servlets/ProjectDocumentList ) is exactly the jdk1.6 introduced classes.

In my experience a useful scheduling for the events is "forget the old events and do the current one". This makes possible to write very responsive interface in case you need e.g. to show something that depends on mouse position: often is NOT necessary to process the entire (maybe long) event queue. If the task to be performed is heavy, "forgetting old events" is a very good solution (if it is possible, of course!)
I successfully implemented this scheduling for an application I developed, but I had to do my custom threading (in fact with the "old" SwingWorker you have to do a similar work by yourself).
It is not clear (to me) if the use of ThreadPoolExecutor in the "new" SwingWorker is flexible enough to make the "forget the old events and do the current one" scheduling immediate. If this is NOT the case, I suggest to add such a feature. This will simplify the building of responsive GUI, and will improve java reputation, too often believed a "too slow language".

Of course, if something is needed, I am happy to contribute, eg writing and submitting my code (where?)

Bye,
;Davide

idk
Offline
Joined: 2005-01-12
Points: 0

Hi,

>In my experience a useful scheduling for the events is "forget the old events and do the current one". This makes possible to write very responsive interface in case you need e.g. to show something that depends on mouse position: often is NOT necessary to process the entire (maybe long) event queue. If the task to be performed is heavy, "forgetting old events" is a very good solution (if it is possible, of course!)

There are two things which might become obsolete.
1. background task could be obsolete itself.
For example background task is weather forecast for 12pm. At 12pm this background task becomes obsolete.
If it is not done by 12pm one wants to cancel it.

SwingWorker can be implemented to support cancellation.( there is an example in the documentation )
One can cancel SwingWorker explicitly or have custom ExecutorService which will support cancellation of all currently submitted tasks and run SwingWorkers in this ExecutorService.

2. partial result produced by the background task could be obsolete.
For example background task continuously reads data from thermometer and displays result in some swing component.
Say we read data from thermometer faster than we display it. One might want to display the latest reading only.

Using SwingWorker one can implement this like:
[code]
doInBackground() {
while(true) {
publish(getThermometerData());
}
}
process(Integer... chunks) {
label.setText(chunks[chunks.length - 1]);
}
[/code]

> I'm new here, so I'm still a little bit confused about projects and I don't know if this (I mean: https://swingworker.dev.java.net/servlets/ProjectDocumentList ) is exactly the jdk1.6 introduced classes.

At this point there is only one difference : SwingWorker from swingworker.dev.java.net implements Runnable and Future where is SwingWorker from jdk1.6 implements RunnableFuture (RunnableFuture implements Runnable and Future and was introduced since jdk1.6)

Thanks,
Igor

delvento
Offline
Joined: 2005-11-21
Points: 0

Thanks to both for reply.

> There are two things which might become obsolete.
> 1. background task could be obsolete itself.
> For example background task is weather forecast for
> 12pm. At 12pm this background task becomes obsolete.
>
> If it is not done by 12pm one wants to cancel it.
>
> SwingWorker can be implemented to support
> cancellation.( there is an example in the
> documentation )
Ok (I think this will be used mainly with "cancel" buttons).

> One can cancel SwingWorker explicitly or have custom
> ExecutorService which will support cancellation of
> all currently submitted tasks and run SwingWorkers in
> this ExecutorService.
This is similar to what I have in mind...

> 2. partial result produced by the background task
> could be obsolete.
> For example background task continuously reads data
> from thermometer and displays result in some swing
> component.
This in interesting, but is another story...

Consider the following example, that is similar to the one I solved: depending on the mouse position on a image (here simple the "Move the mouse here" words) I draw something on another panel (here simply the mouse x coordinate). The heavy calculation on the image are simulated with the sleep().

In this situation the "standard" SwingWorker behavior, is (in some way) worse than the simpler code in AWT thread: try to move the mouse a lot!
I don't understand if there is a *simple* way to use SwingWorker in order to obtain what I want: process only the last available event.

The solution I used for my application: my own class (which can be used like SwingWorker) that start a thread which process only the last event. There is not any queue. My class is simple, but is not completely trivial: a little bit of thread synchronization is required.

I think that a *simple* way to handle similar situation should exist in SwingWorker, don't you?
If so (and if there isn't any *simple* alternative yet) I think that I can contribute with my code to the current SwingWorker implementation.

Bye,
;Davide

import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;

import javax.swing.SwingWorker;

public class mySwingWorkerExample {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}

private static void createAndShowGUI() {
//Create and set up the window
JFrame frame = new JFrame("mySwingWorkerExample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JPanel newContentPane = buildContent();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);

//Display the window.
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
}

private static JPanel buildContent() {
JPanel ritorna = new JPanel();
JTabbedPane tab_pan = new JTabbedPane();

for (dispatch_policies policy : dispatch_policies.values())
{
// construct the container
JPanel panel = new JPanel(new java.awt.GridLayout(3,1));

// construct the description
JTextField descr = new JTextField(policy.getDescription());
descr.setEditable(false);
descr.setHorizontalAlignment(JTextField.CENTER);
panel.add(descr);

// construct the responce field and assign to the listener
JTextField responce = new JTextField("50");
responce.setEditable(false);
responce.setHorizontalAlignment(JTextField.CENTER);
panel.add(responce);
policy.setTheResultIn(responce);

// construct the active field and activate the listener
JTextField mouseHere = new JTextField("Move the mouse here");
mouseHere.setEditable(false);
mouseHere.setHorizontalAlignment(JTextField.CENTER);
panel.add(mouseHere);
mouseHere.addMouseMotionListener(policy);

panel.setPreferredSize(new java.awt.Dimension(400,300));
tab_pan.add(policy.toString(), panel);
}

ritorna.add(tab_pan);
return ritorna;
}
}

enum dispatch_policies implements MouseMotionListener {
EXAMPLE {
void doSomething(MouseEvent e, JTextComponent myTxt) {
myTxt.setText(String.valueOf(e.getX()));
}

public String getDescription() {
return "This is good for fast elaborations " +
"in the AWT thread...";
}
},
AWT_STD {
void doSomething(MouseEvent e, JTextComponent myTxt) {
sleep();
myTxt.setText(String.valueOf(e.getX()));
}

public String getDescription() {
return "But what's about if a heavy " +
"task is needed?";
}
},
NEW_THREAD {
void doSomething(final MouseEvent e, final JTextComponent myTxt) {
final SwingWorker worker = new SwingWorker() {
public String doInBackground() {
sleep();
return "well done!";
}
protected void done() {
myTxt.setText(String.valueOf(e.getX()));
}
};
worker.execute();
}

public String getDescription() {
return "Nice, but a the new thread " +
"will execute ALL the events.";
}
};
private static int delayTime = 500;
private JTextComponent myTxt = new JTextField();
public void setTheResultIn(JTextComponent who) {
myTxt = who;
}

public void mouseMoved(MouseEvent e) {
doSomething(e, myTxt);
}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {}
public void mouseDragged(MouseEvent e) {}

private static void sleep() {
try {
Thread.sleep(delayTime);
}
catch(InterruptedException e)
{
//sleep something less, but tell me why
e.printStackTrace();
}
}

public abstract String getDescription();
abstract void doSomething(MouseEvent e, JTextComponent myTxt);
}

idk
Offline
Joined: 2005-01-12
Points: 0

Hi,

>Consider the following example, that is similar to the one I solved: depending on the mouse position on a image (here simple the "Move the mouse here" words) I draw something on another panel (here simply the mouse x coordinate). The heavy calculation on the image are simulated with the sleep().
>In this situation the "standard" SwingWorker behavior, is (in some way) worse than the simpler code in AWT thread: try to move the mouse a lot!
I don't understand if there is a *simple* way to use SwingWorker in order to obtain what I want: process only the last available event.

SwingWorker has internal ThreadPoolExecutor. There are a number of worker threads available in this ExecutorService. SwingWorker.doInBackground is executed in a worker thread. Multiple SwingWorkers can run in parallel. If internal ThreadPoolExecutor can not run SwingWorker right away it queues it and run as soon as there is a worker thread available.

Your example starts new SwingWorker on every mouse event. All of them are executed and display result. Because there are so many SwingWorkers submitted for execution it takes so much time to finally see the result. (By the way the result one finally see on the screen is not necessarily produced by the last SwingWorker. There is no guarantee that SwingWorkers finish in the same order they are executed.)

Moving mouse obsoletes all the previously running background tasks. For this case one needs only last SwingWorker to finish and all others can be canceled as soon as the new one starts. In fact one needs no more than one SwingWorker running at all.


to post code with indentation one can wrap code
into [b][[/b]code[b]] [[/b]/code[b]][/b] brackets.

[code]
import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;

import javax.swing.SwingWorker;

public class mySwingWorkerExample {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}

private static void createAndShowGUI() {
/* Create and set up the window */
JFrame frame = new JFrame("mySwingWorkerExample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JPanel newContentPane = buildContent();
newContentPane.setOpaque(true); /* content panes must be opaque */
frame.setContentPane(newContentPane);

/* Display the window. */
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
}

private static JPanel buildContent() {
JPanel ritorna = new JPanel();
JTabbedPane tab_pan = new JTabbedPane();

for (dispatch_policies policy : dispatch_policies.values()) {
/* construct the container */
JPanel panel = new JPanel(new java.awt.GridLayout(3, 1));

/* construct the description */
JTextField descr = new JTextField(policy.getDescription());
descr.setEditable(false);
descr.setHorizontalAlignment(JTextField.CENTER);
panel.add(descr);

/* construct the responce field and assign to the listener */
JTextField responce = new JTextField("50");
responce.setEditable(false);
responce.setHorizontalAlignment(JTextField.CENTER);
panel.add(responce);
policy.setTheResultIn(responce);

/* construct the active field and activate the listener */
JTextField mouseHere = new JTextField("Move the mouse here");
mouseHere.setEditable(false);
mouseHere.setHorizontalAlignment(JTextField.CENTER);
panel.add(mouseHere);
mouseHere.addMouseMotionListener(policy);

panel.setPreferredSize(new java.awt.Dimension(400, 300));
tab_pan.add(policy.toString(), panel);
}

ritorna.add(tab_pan);
return ritorna;
}
}

enum dispatch_policies implements MouseMotionListener {

EXAMPLE {
void doSomething(MouseEvent e, JTextComponent myTxt) {
myTxt.setText(String.valueOf(e.getX()));
}

public String getDescription() {
return "This is good for fast elaborations "
+ "in the AWT thread...";
}
},
AWT_STD {
void doSomething(MouseEvent e, JTextComponent myTxt) {
sleep();
myTxt.setText(String.valueOf(e.getX()));
}

public String getDescription() {
return "But what's about if a heavy " + "task is needed?";
}
},
NEW_THREAD {
void doSomething(final MouseEvent e, final JTextComponent myTxt) {
if (worker != null) {
worker.cancel(true);
}
worker = new SwingWorker() {
public String doInBackground() throws Exception {
sleep();
return "well done!";
}

protected void done() {
if (!isCancelled()) {
myTxt.setText(String.valueOf(e.getX()));
}
}
};
worker.execute();
}

public String getDescription() {
return "Nice, but a the new thread "
+ "will execute ALL the events.";
}
};
private static SwingWorker worker = null;

private static int delayTime = 500;

private JTextComponent myTxt = new JTextField();

public void setTheResultIn(JTextComponent who) {
myTxt = who;
}

public void mouseMoved(MouseEvent e) {
doSomething(e, myTxt);
}

public void mouseEntered(MouseEvent e) {
}

public void mouseExited(MouseEvent e) {
}

public void mouseClicked(MouseEvent e) {
}

public void mouseDragged(MouseEvent e) {
}

private static void sleep() {
try {
Thread.sleep(delayTime);
} catch (InterruptedException e) {
/* we can be interrupted if cancel is invoked */
}
}

public abstract String getDescription();

abstract void doSomething(MouseEvent e, JTextComponent myTxt);
}
[/code]

Thanks,
Igor

Message was edited by: idk

alexlamsl
Offline
Joined: 2004-09-02
Points: 0

yeah yeah.... look at those green code all over the place!

(no offense - not towards you anyway ;) )

zander
Offline
Joined: 2003-06-13
Points: 0

From where I am standing the concept of one thread that manages swing objects is just fine; it really is a good concept!

Thing you are missing is a layer on top; a simple way to manage actions and workers. I see solutions popping up on all sides; some better then others.
I really doubt the JDK should solve this problem; the various open source libraries will do a much better job over time.

I made a full-scale solution myself (part of the UICompiler project) which I (naturally) think is better then all te others; competition is healthy :)

tim12s
Offline
Joined: 2004-02-02
Points: 0

YES yes yes.

Management of actions, long running actions, actions on popups sensitive to JTree, etc. Management of actions relating to plugins, etc.

Its a common problem which every complex app has to deal with. I'd hesitate to say what the best 'solution' is, and I would hesitate even more regarding putting a less than ideal solution into the JDK, but its definately worthwhile.

Definately consider putting Swixml (or a derivative) into the JDK and see how it could be extended further. Adding SWT support to Swixml may 'satisfy' the SWT crowd.

zander
Offline
Joined: 2003-06-13
Points: 0

> Definately consider putting Swixml (or a derivative)
> into the JDK and see how it could be extended
> further. Adding SWT support to Swixml may 'satisfy'
> the SWT crowd.

Why should it be placed in the JDK? Whats wrong with using an external library and shipping that to your clients?

tim12s
Offline
Joined: 2004-02-02
Points: 0

I was thinking about that a while back.

In the current environment, something gains credibility being included in the JDK. At the same time there is the problem that the JDK seems to grow. Deprecated stuff never gets removed, etc.

Maybe some sort of 'application stack' of some sort. What is really wanted from a technology point of view is good documentation, active development and support of issues/problems. At the moment it seems as if 'side' projects/experiments get mothballed after a while.

tim12s
Offline
Joined: 2004-02-02
Points: 0

Hmm... mincing my words.

Maybe some sort of 'application stack' should be pushed by Sun. Include in this stack things like Java Help, Swixml, etc.

zander
Offline
Joined: 2003-06-13
Points: 0

My thoughts exactly;
see: "Great Suggestion!" in: http://forums.java.net/jive/thread.jspa?messageID=2407

denismo
Offline
Joined: 2003-06-28
Points: 0

Yes, the problems of EDT blockage that you described can be solved using Foxtrot. We are planning to export some public APIs in Mustang that will allow you to do the same.

As for threading model, it can not be changed - it will be one of the most backward-incompatible changes. So, whenever you want to update Swing GUI, you must use invokeLater.