Is it required to access Swing properties in EDT?
On the one hand, it is said that:
In general Swing is not thread safe. All Swing components and related classes, unless otherwise documented, must be accessed on the event dispatching thread.
On the other hand, I can freely invoke, for example, getText()
on JTextComponent
s with no exceptions thrown even if I explicitly install a FailOnThreadViolationRepaintManager
(see this page for more info).
import org.fest.swing.edt.FailOnThreadViolationRepaintManager;
import org.fest.swing.edt.GuiActionRunner;
import org.fest.swing.edt.GuiQuery;
import org.fest.swing.exception.EdtViolationException;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import javax.swing.JTextField;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
class EdtTest {
@BeforeAll
static void beforeAll() {
FailOnThreadViolationRepaintManager.install();
}
@Test
void settingThrows_butAccessingDoesNot() {
JTextField textField = GuiActionRunner.execute(new GuiQuery<JTextField>() {
@Override
protected JTextField executeInEDT() {
return new JTextField();
}
});
String text = "Some text";
assertThrows(EdtViolationException.class, () -> textField.setText(text));
assertDoesNotThrow(() -> textField.getText());
}
}
If I can get away with it, I would rather avoid wrapping each getter call in some EDT-posting lambda in my tests (even if I make it easier with some utility method, like callAndWait(field::getText)
).
Java 8.
It's a rule that you can't touch Swing components from non-EDT threads. The rule is there to help you not create future messes. You CAN invoke methods from other threads, and it won't error. But, you will create very very difficult defects doing this. So the rule is there to spare you the pain of trying to recreate a defect that only happens sometimes under strange conditions that your users will never correctly tell you what they did. And even if they can tell you exactly what they did and what the context is, you still may not be able to reproduce it because threading errors, race conditions, and dead reckoning errors are where careers go to die. As I tell my kids you can learn by listening or you can learn the hard way. Your choice, but you will understand what I mean one way or the other.
Now, I think you are going about things in a way that is creating this dynamic that is working against your best interest. For one, what are you doing that requires you to interrogate Swing components from other threads? This is a serious code smell, and probably anti-pattern. If you change your approach you can help yourself instead of hurting yourself. For example, if you want to do something on another thread that might require invoking methods on Swing Components you should encapsulate that state in a message, and send that information to the non-EDT thread. It computes the answer, and posts that result back at the EDT thread.
BlockingQueue queue = ....;
void someAction(ActionEvent evt) {
ComputeThisForMeMessage msg = new ComputeThisForMeMessage(
comp.getState(),
.... );
queue.put( msg );
}
Then in your non-EDT thread:
BlockingQueue queue = ...;
void execute() {
while( !Thread.isInterrupted() ) {
Message msg = queue.take(); // or poll with a timeout so you can quit without something being posted to the queue.
msg.execute(); // perform work on the non-EDT thread
SwingUtilities.invokeLater( new Runnable() {
public void run() {
msg.handleResult();
}
});
}
}
Your message class can encapsulate messages that perform work off the EDT thread, and update the results in a separate method like so:
public interface Message {
void execute(); // performed off EDT
void handleResult(); // performed on EDT
}
This is similar to SwingWorker
, but it allows for more control over the threading and such. You could use a thread pool, or a single thread without changing anything. But, the idea is that you send all of the data the thread needs in a thread-safe manner. Pull data from the swing components at the time of the action invocation from the EDT thread, then let the long running computation happen off EDT, and post those results back to the EDT to update the UI. So making network hops, long running algos, etc can be performed off the EDT thread keeping the UI live and thread safe.
You probably could use lambdas to make it easier to define off-EDT-thread on-EDT-thread behavior. Naturally because there are 2 methods anonymous inner classes make sense here. But, some people balk at using them because of verbosity. So a lambda that returns a lambda could work as well.
public interface BackgroundTask<P,R> {
Callable<R> execute(P parameter);
}
That would be something like this to use it:
final var someUIData = comp.getData(); // Read swing component state here on the EDT. Save it to a variable then you can safely work with it in the thread.
queue.put( (p) => {
// do work here off EDT thread
someUIData.blah blah blah
return () => {
// this is invoked on EDT thread
R r = ...;
return r;
};
});
That could work, but you'll need to be super careful about accessing parameters in the body of the lambda method. I think it's generally safe since the non-EDT thread will return this lambda at the end of its work, and can't access those objects from that thread. But you'd need to make sure you weren't accessing swing components in the first lambda. Using immutable objects for things like someUIData
will also help you. If someUIData
isn't immutable you may have threading issues with those objects. But, UI paradigms of interaction here can disable or enable the errors more or less. It can depend on your UI structure, but something to consider. So there is less boiler-plate code, but more care is needed to make sure you are following the rules.
There are lots of ways to architect this, but something like this will make it easier to follow the rules without a lot of pain. That's what good architecture should do. Keep you working quickly and enforcing good patterns of working.