问题
My understanding of the Swing Event Dispatcher Thread (EDT) is that its a dedicated thread where event handling code is executed. So, if my understanding is correct, then in the example below:
private class ButtonClickListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
// START EDT
String command = e.getActionCommand();
if( command.equals( "OK" )) {
statusLabel.setText("Ok Button clicked.");
} else if( command.equals( "Submit" ) ) {
statusLabel.setText("Submit Button clicked.");
} else {
statusLabel.setText("Cancel Button clicked.");
}
// END EDT
}
}
All the code in between START EDT
and END EDT
is executing on the EDT, and any code outside of it is executing on the main application thread. Similarly, another example:
// OUTSIDE EDT
JFrame mainFrame = new JFrame("Java SWING Examples");
mainFrame.setSize(400,400);
mainFrame.setLayout(new GridLayout(3, 1));
mainFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent windowEvent){
// START EDT
System.exit(0);
// END EDT
}
// BACK TO BEING OUTSIDE THE EDT
});
Again, only the System.exit(0)
is executed inside the EDT.
So for starters, if my understanding of the "division of labor" between EDT and main app thread code execution is incorrect, please begin by correcting me!
Now then, I came across an article that emphasized the use of creating a new Thread
from inside all this EDT code, which would make my first example above look like this:
public class LabelUpdater implements Runnable {
private JLabel statusLabel;
private ActionEvent actionEvent;
// ctor omitted here for brevity
@Override
public void run() {
String command = actionEvent.getActionCommand();
if (command.equals( "OK" )) {
statusLabel.setText("Ok Button clicked.");
} else if( command.equals( "Submit" ) ) {
statusLabel.setText("Submit Button clicked.");
} else {
statusLabel.setText("Cancel Button clicked.");
}
}
}
private class ButtonClickListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
// START EDT
Thread thread = new Thread(new LabelUpdater(statusLabel, e));
thread.start();
// END EDT
}
}
My question: what advantage (or lack thereof) is there to this approach? Should I always code my EDT code this way, or is there a rubric one needs to follow as a guidelines for when to apply it? Thanks in advance!
回答1:
The question is a bit broad and unspecific, but I'll try to address some of the points that you asked about. The entry point for further, own research is probably the Lesson: Concurrency in Swing, although it may indeed be hard to derive definite statements for specific cases from that.
First of all, there is an overarching rule in Swing - referred to as the Single Thread Rule:
Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread.
(Unfortunately, it is no longer stated so clearly in the tutorial)
Keeping that in mind, looking at your snippets:
// OUTSIDE EDT
JFrame mainFrame = new JFrame("Java SWING Examples");
...
This is often true, unfortunately - and unfortunately, even in some of the official Swing examples. But this may already cause problems. To be on the safe side, the GUI (including the main frame) should always be handled on the EDT, using SwingUtilities#invokeLater. The pattern is always the same then:
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui() {
JFrame mainFrame = new JFrame("Java SWING Examples");
...
mainFrame.setVisible(true);
}
Regarding the second example that you showed, involving the LabelUpdater
class: I'd be curious from which article you got this. I know, there is a lot of cr4p out there, but this example doesn't even remotely make sense...
public class LabelUpdater implements Runnable {
private JLabel statusLabel;
...
@Override
public void run() {
...
statusLabel.setText("Ok Button clicked.");
}
}
If this code (i.e. the run
method) is executed in an new thread, then it obviously violates the single thread rule: The status of the the JLabel
is modified from a thread that is not the event dispatch thread!
The main point of starting a new thread in an event handler (e.g. in an actionPerformed
method of an ActionListener
) is to prevent blocking the user interface. If you had some code like this
someButton.addActionListener(e -> {
doSomeComputationThatTakesFiveMinutes();
someLabel.setText("Finished");
});
then pressing the button would cause the EDT to be blocked for 5 minutes - i.e. the GUI would "freeze", and look like it hung up. In these cases (i.e. when you have long-running computations), you should do the work in an own thread.
The naive approach of doing this manually could (roughly) look like this:
someButton.addActionListener(e -> {
startBackgroundThread();
});
private void startBackgroundThread() {
Thread thread = new Thread(() -> {
doSomeComputationThatTakesFiveMinutes();
someLabel.setText("Finished"); // WARNING - see notes below!
});
thread.start();
}
Now, pressing the button would start a new thread, and the GUI would no longer block. But note the WARNING
in the code: Now there's this problem again of the JLabel
being modified by a thread that is not the event dispatch thread! So you'd have to pass this back to the EDT:
private void startBackgroundThread() {
Thread thread = new Thread(() -> {
doSomeComputationThatTakesFiveMinutes();
// Do this on the EDT again...
SwingUtilities.invokeLater(() -> {
someLabel.setText("Finished");
});
});
thread.start();
}
This may look clumsy and complicated, and as if you could have a hard time figuring out on which thread you currently are. And that's right. But for the common task of starting a long-running task, there is the SwingWorker class explained in the tutorial that makes this pattern somewhat simpler.
Shameless self-promotion: A while ago, I created a SwingTasks library, which is basically a "Swing Worker on steroids". It allows you to "wire up" methods like this...
SwingTaskExecutors.create(
() -> computeTheResult(),
result -> receiveTheResult(result)
).build().execute();
and takes care of showing a (modal) dialog if the execution takes too long, and offers some other convenience methods, e.g. for showing a progress bar in the dialog and so on. The samples are summarized at https://github.com/javagl/SwingTasks/tree/master/src/test/java/de/javagl/swing/tasks/samples
来源:https://stackoverflow.com/questions/58099010/starting-threads-from-inside-edt-event-handler-code-in-swing-apps