开发者

Java/Swing: the fast/slow UI binding problem

I need a way to bind UI indicators to ra开发者_如何学Cpidly-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?


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");
         }
     }
 }


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."


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.


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();
        }
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜