简体   繁体   中英

Implementing threading in Swing's EDT?

My project is built upon Java's Swing library. It spawns the EDT which displays my GUI (which works correctly).

The entrance to the program, which initializes the EDT:

public final class Main {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Start());
    }

    class Start implements Runnable {
        private Model model = new Model();
        private Controller controller = new Controller(model);
        private View view = new View(controller);

        @Override
        public void run() {
            // Initialize the view and display its JFrame...
        }
    }
}

}

However, when a button / radio box / etc. is clicked within my GUI, the Controller class must perform an action on the model.

My questions are the following:

  • Should I wrap the controller's code in a new SwingWorker?
    • If no, should I wrap my model's code in a new SwingWorker?
  • If I wrap the controller's code with threads, do I need to synchronize the shared state variables within my model?
  • If my model, running on a new thread, notifies my GUI of changes, will this occur on the EDT or on the new thread?

For example:

public class Controller {
    public void updateModel() {
        new SwingWorker<Void, Void>() {
            @Override
            protected Void doInBackground() throws Exception {
                model.somethingSomethingSomething();
            }
        }.execute();
    }
}

public class Model {
    public void somethingSomethingSomething() {
        notifyListeners(); // This is going to notify whichever GUI 
                           // is listening to the model.
                           // Does it have to be wrapped with Swing.invokeLater?
    }
}

public class View {
    // This function is called when the model notifies its listeners.
    public void modelChangedNotifier() {
        button.setText("THE MODEL HAS CHANGED"); // Does this occur on the EDT?
    }
}

You can read about it here: Improve Application Performance With SwingWorker in Java SE 6 . In short: all time consuming operations, which are not affected UI must be done in another thread . To show results of operation you must go back to EDT. For example, if you make database search, you should show a progress bar (usually infinite) and start the search using SwingWorker. To show search results in a table, you must be in EDT. Alternatively you can use foxtrot lib (it allows to make your code more Swing convenient without to redesign it). If your controller code permanently updates the swing widgets you should execute it in EDT or at least perform these updates of UI in EDT (using SwingUtilities.invokeLater, chunk processing in SwingWorker or swing.Timer). So your sample is wrong: model update should be up-to-date in EDT.

Instead of updating your model from doInBackground() , publish() interim results and update your model from process() , which executes on the EDT. In this example , JTable corresponds to your View and TableModel corresponds to your Model . Note that JTable listens to its own TableModel .

One alternative approach, from Java Concurrency in Practice 9.4.2, uses a "Split" or a "Shared Data Model". You update your Business Model on whatever thread you want, likely the long-running non-EDT thread. But then, instead of directly calling notifyListeners() and worrying about which thread you are on, simply call myComponent.repaint(), which will queue up a repaint request on the EDT .

Then, somewhere in your paintComponent() method, you explicitly grab all new data from the Model, typically in a method called modelToView()

   commentTextArea.setText(myModel.getCommentText());
   fooLabel.setText(myModel.getFooText());
   ...

The upsides are that threading is not an issue, and, at least to some minds, this "makes sense", and the model is nicely decoupled from the view. A downside is that you are resetting all the values every time. So if you have 100 JComponents, that's 100 things getting set. Also, the view is pretty tightly coupled to the model.


Working Code Examples

@MadProgrammer and @kleopatra are correct that, if the view directly contains the components that are being updated, you get an "infinite loop of doom". For proof, see

Demo_14716901_Fails

However, if the view is isolated from the components, you avoid the infinite loop. Normally, the higher level view would contain stuff like JSplitPanes, holding JScrollPanes, holding Boxes or more JPanels, holding the actual low level components. So this requirement, IMO, is not unreasonable.

Working code at Demo_14716901_Works

Some Comments Added for the Downvoters :

Some people want to defeat Swing . They are writing instrument control code or algorithms and just want to get their job done without worrying about the EDT, endless SwingWorkers and invokeLaters. This technique lets them get their job done. With the one important caveat noted, it works. (Personally, I understand and generally like Swing, but many don't).

While Swing components are nicely MVC, they are generally at far too micro a level. The "real" model is not a single String, it is dozens of values. The "real" view is not a single JLabel, it is many JPanels, each with many components, combined with scrollers, splitters, etc. This technique usually fits the real world better, allowing the programmer to think naturally at a higher level.

As far as "bad practice", take it up with Brian Goetz, Josh Bloch, etc. ok, that's "appeal to authority", but it works for me. :-)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM