views:

260

answers:

4

I need a way to bind UI indicators to rapidly-changing values.

I have a class NumberCruncher which does a bunch of heavy processing in a critical non-UI thread, thousands of iterations of a loop per second, and some number of those result in changes to a set of parameters I care about. (think of them as a key-value store)

I want to display those at a slower rate in the UI thread; 10-20Hz would be fine. How can I add MVC-style notification so that my NumberCruncher code doesn't need to know about the UI code/binding?

+2  A: 

Have a single object which your NumberCrucher modifies/keeps on changing based on the numerous operations you do. Let that run in a separate thread. Have a UI in swing which uses the same Object that NumberCruncher modifies. This thread is going to only read the values at specified time period so it should not be a problem of thread deadlocks.

NumberCruncher

public class NumberCruncher implements Runnable{
 CommonObject commonObj;
 public NumberCruncher(CommonObject commonObj){
  this.commonObj = commonObj;
 }
 public void run() {
  for(;;){
   commonObj.freqChangeVal = Math.random();
  }
 }
}

CommonObject:

public class CommonObject {
 public double freqChangeVal;
}

UI:

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class UI extends JFrame implements Runnable{

    private CommonObject commonObj = new CommonObject();

    JLabel label ;

    public static void main(String args[]){
        UI ui = new UI();
        ui.begin();
        Thread t2 = new Thread(ui);
        t2.start();
    }

    private void begin(){
        JPanel panel = new JPanel();
        label = new JLabel("Test");
        panel.add(label);

        Thread thread = new Thread(new NumberCruncher(commonObj));
        thread.start();

        this.add(panel);
        this.setSize(200,200);
        this.setVisible(true);
    }

    public void run() {
        for(;;){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            label.setText(commonObj.freqChangeVal+"");
            this.repaint();
        }
    }
}
Bragboy
-1: a) CommonObject's freqChangeVal field is not volatile and hence changes by one thread may not be visible on another. b) The UI run() method is not being called on the Event Dispatch thread meaning that the JLabel is being modified by another thread (which should not happen). c) This is reinventing the wheel; a more appropriate mechanism would be to use a SwingWorker with calls to publish / process to notify the GUI periodically on the Event Dispatch Thread.
Adamski
@Adamski : Thank you for spending time to indicate the problems in my program.
Bragboy
No problem - You're on the right lines; Concurrency is tricky to get right so definitely worth using any classes already provided.
Adamski
+2  A: 

Seems like you might want to take the "Listener" approach. Allow your number cruncher to register listeners, then every 100-200 loops (configurable) (or on some change condition), notify the listeners that there is an update they should be aware of.

The listener can be another class that has a thread wait() ing on it, and when it gets notified, it just updates its internal variable, then notifies the waiting thread. The fast loop class then has a quick way to update an external value and not worry about access to its fast changing internal state.

The other thread that wait()s can also have a wait() on a timer thread that is set to 10-20HZ (configurable) to wait on the timer before wait()ing on the next update from your synchronized class.

Zak
+3  A: 

The idiomatic way to do this is to use the SwingWorker class, and to use calls to publish(V...) to notify the Event Dispatch thread periodically causing it to update the UI.

In the below example taken from the Javadoc the number crunching takes place on a worker thread in the doInBackground() method, which calls publish on each iteration. This call causes the process(V...) method to be called asynchronously on the Event Dispatch thread allowing it to update the UI. Note that this ensures that the user interaface is always updated from the Event Dispatch thread. Also note that you may choose to call publish every N iterations to reduce the frequency at which the user interface is updated.

Example From Javadoc

 class PrimeNumbersTask extends 
         SwingWorker<List<Integer>, Integer> {
     PrimeNumbersTask(JTextArea textArea, int numbersToFind) { 
         //initialize 
     }

     @Override
     public List<Integer> doInBackground() {
         while (! enough && ! isCancelled()) {
                 number = nextPrimeNumber();
                 publish(number);
                 setProgress(100 * numbers.size() / numbersToFind);
             }
         }
         return numbers;
     }

     @Override
     protected void process(List<Integer> chunks) {
         for (int number : chunks) {
             textArea.append(number + "\n");
         }
     }
 }
Adamski
so publish() and process() deal with queues? interesting....
Jason S
+2  A: 

SwingWorker, suggested by @Adamski, is preferable; but an instance of javax.swing.Timer is a convenient alternative for this, as "the action event handlers for Timers execute [on] the event-dispatching thread."

trashgod