views:

312

answers:

3

As is widely known, anything related to Swing components must be done on the event dispatch thread. This also applies to the models behind the components, such as TableModel. Easy enough in elementary cases, but things become pretty complicated if the model is a "live view" of something that must run on a separate thread because it's changing quickly. For example, a live view of a stock market on a JTable. Stock markets don't usually happen on the EDT.

So, what is the preferable pattern to (de)couple the Swing model that must be on the EDT, and a "real", thread-safe model that must be updateable from anywhere, anytime? One possible solution would be to actually split the model into two separate copies: the "real" model plus its Swing counterpart, which is is a snapshot of the "real" model. They're then (bidirectionally) synchronized on the EDT every now and then. But this feels like bloat. Is this really the only viable approach, or are there any other, or more standard, ways? Helpful libraries? Anything?

+2  A: 

As far as I understand, you don't want to implement Swing model interfaces in your real model, do you? Can you implement a Swing model as a "view" over a part of a real model? It will translate its read-access getValueAt() to the calls of the real model, and the real model will notify Swing model about the changes , either providing a list of changes or assuming that Swing model will take care of quering the new values of everything it currently is showing.

Dmitry
Right: I don't want to implement Swing interfaces in my real model, because I know that the real model will be updated from threads other that the EDT, which would violate Swing's requirements. I think what you're describing is essentially the "split model".
Joonas Pulakka
It is not a big problem to update the real model from other threads, provided that interaction with Swing happens in EDT and your model is thread-safe. Swing will call methods like getValueAT() in EDT so the only thing to worry about is sending notifications (fireSmthChanged) in EDT. You can do it using SwingUtilities.invokeLater
Dmitry
... but of course you will be facing subtle issues, like wrong number of children in a tree (because something has changed since you sent a notification to Swing), etc.
Dmitry
Subtle issues are to be expected. But note that if the Swing calls getValueAt(x) on the real model, *the value might not exist any more*, if it was removed by a different thread just a moment ago. So this could lead to `ArrayIndexOutOfBoundsException`s, which is not subtle.
Joonas Pulakka
That's what I actually meant as "subtle". It is of course not that subtle if it is not handled properly :). Another idea: you may try creating snapshots of the changed parts and use them if something changes between the instants when you notify Swing and it refreshes its data. But this might be much more error prone than "split the model".
Dmitry
Ok, handling nonexistent get()s gracefully could be one option. Actually I'm starting to see the different trade-offs here. "Split model" is a brute force approach, conceptually relatively simple and robust, but a resource hog (if the snapshot is taken on each update). Incremental notification would be way lighter, but also way harder to implement correctly.
Joonas Pulakka
+2  A: 

The usual approach is to send "signals" of some kind to which the UI listens. In my code, I often use a central dispatcher which sends signals that contain the object that was modified, the name of the field/property plus the old and new value. No signal is sent for the case oldValue.equals(newValue) or oldValue.compareTo(newValue) == 0 (the latter for dates and BigDecimal).

The UI thread then registers a listener for all signals. It then examines the object and the name and then translates that to a change in the UI which is executed via asyncExec().

You could turn that into a listener per object and have each UI element register itself to the model. But I've found that this just spreads the code all over the place. When I have a huge set of objects on both sides, I sometimes just use several signals (or events) to make things more manageable.

Aaron Digulla
Ok, so you have a split model, and then send update notifications from the "real" model to the UI model. `asyncExec()` seems to be a SWT function. Is it essentially similar to `EventQueue.invokeLater()`?
Joonas Pulakka
Yep, use `EventQueue.invokeLater()` :) My approach is to keep the dependencies between the two models as slim as possible, so I can easily change either. As long as the signals don't change (much), the other model won't be affected.
Aaron Digulla
+4  A: 

I can recommend the following approach:

  • Place events that should modify the table on a "pending event" queue, and when an event is placed on the queue and the queue is empty then invoke the Event Dispatch thread to drain the queue of all events and update the table model. This optimisation means you are no longer invoking the event dispatch thread for every event received, which solves the problem of the event dispatch thread not keeping up with the underlying event stream.
  • Avoid creation of a new Runnable when invoking the event dispatch thread by using a stateless inner class to drain the pending event queue within your table panel implementation.
  • Optional further optimisation: When draining the pending event queue minimise the number of table update events fired by remembering which table rows need to be repainted and then firing a single event (or one event per row) after processing all events.

Example Code

public class MyStockPanel extends JPanel {
  private final BlockingQueue<StockEvent> stockEvents;

  // Runnable invoked on event dispatch thread and responsible for applying any
  // pending events to the table model.
  private final Runnable processEventsRunnable = new Runnable() {
    public void run() {
      StockEvent evt;

      while ((evt = stockEvents.poll() != null) {
        // Update table model and fire table event.
        // Could optimise here by firing a single table changed event
        // when the queue is empty if processing a large #events.
      }
    }
  }

  // Called by thread other than event dispatch thread.  Adds event to
  // "pending" queue ready to be processed.
  public void addStockEvent(StockEvent evt) {
    stockEvents.add(evt);

    // Optimisation 1: Only invoke EDT if the queue was previously empty before
    // adding this event.  If the size is 0 at this point then the EDT must have
    // already been active and removed the event from the queue, and if the size
    // is > 0 we know that the EDT must have already been invoked in a previous
    // method call but not yet drained the queue (i.e. so no need to invoke it
    // again).
    if (stockEvents.size() == 1) {
      // Optimisation 2: Do not create a new Runnable each time but use a stateless
      // inner class to drain the queue and update the table model.
      SwingUtilities.invokeLater(processEventsRunnable);
    }
  }
}
Adamski
Thanks, you definitely seem to have been there :-)
Joonas Pulakka
By the way, that code has a subtle bug: if someone adds a StockEvent just when the queue is about to be empty, stockEvents.isEmpty() may return false but the processEventsRunnable ends before the new events get added to stockEvents queue. Then it's in stuck state. A solution could be to use other mechanism than isEmpty() to check whether processEventsRunnable needs to be relaunched.
Joonas Pulakka
@Joonas - That's a really good point. I think the problem is solved by checking that size() == 1 after adding the event to the queue. If the queue is empty at this point then the event has already been processed so no problems there, and if the size is > 1 then we know the EDT must have already been invoked by a previous method call.
Adamski
@Adamski - That's an ingenious solution, thanks again!
Joonas Pulakka
You're welcome!
Adamski