Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clever asynchronous repaint in Java

I have a use-case coming from a GUI problem I would like to submit to your sagacity.

Use case

I have a GUI that displays a computation result depending on some parameters the user set in a GUI. For instance, when the user moves a slider, several events are fired, that all trigger a new computation. When the user adjust the slider value from A to B, a dozens of events are fired.

But the computation can take up to several seconds, whereas the slider adjustment can fire an event every few 100 ms.

How to write a proper Thread that would listen to these events, and kind of filter them so that the repaint of the results is lively? Ideally you would like something like

  • start a new computation as soon as first change event is received;
  • cancel the first computation if a new event is received, and start a new one with the new parameters;
  • but ensure that the last event will not be lost, because the last completed computation needs to be the one with last updated parameters.

What I have tried

A friend of mine (A. Cardona) proposed this low level approach of an Updater thread that prevents too many events to trigger a computation. I copy-paste it here (GPL):

He puts this in a class that extends Thread:

public void doUpdate() {
    if (isInterrupted())
        return;
    synchronized (this) {
        request++;
        notify();
    }
}

public void quit() {
    interrupt();
    synchronized (this) {
        notify();
    }
}

 public void run() {
    while (!isInterrupted()) {
        try {
            final long r;
            synchronized (this) {
                r = request;
            }
            // Call refreshable update from this thread
            if (r > 0)
                refresh(); // Will trigger re-computation
            synchronized (this) {
                if (r == request) {
                    request = 0; // reset
                    wait();
                }
                // else loop through to update again
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


public void refresh() {
    // Execute computation and paint it
    ...
}

Every-time an event is sent by the GUI stating that parameters have been changed, we call updater.doUpdate(). This causes the method refresh() to be called much less. But I have no control on this.

Another way?

I was wondering if there is another way to do that, that would use the jaca.concurrent classes. But I could not sort in the Executors framework what would be the one I should start with.

Does any of you have some experience with a similar use case?

Thanks

like image 922
Jean-Yves Avatar asked Mar 14 '13 14:03

Jean-Yves


1 Answers

If you're using Swing, the SwingWorker provides capabilities for this, and you don't have to deal with the thread pool yourself.

Fire off a SwingWorker for each request. If a new request comes in and the worker is not done, you can cancel() it, and just start a new SwingWorker. Regarding what the other poster said, I don't think publish() and process() are what you are looking for (although they are also very useful), since they are meant for a case where the worker might fire off events faster than the GUI can process it.

ThingyWorker worker;

public void actionPerformed(ActionEvent e) {
    if( worker != null ) worker.cancel();
    worker = new ThingyWorker();
    worker.execute();
}

class ThingyWorker extends SwingWorker<YOURCLASS, Object> {
    @Override protected YOURCLASS doInBackground() throws Exception {
        return doSomeComputation(); // Should be interruptible
    }   
    @Override protected void done() {
        worker = null; // Reset the reference to worker

        YOURCLASS data;

        try {
            data = get();
        } catch (Exception e) { 
            // May be InterruptedException or ExecutionException                
            e.printStackTrace();
            return;
        }           

        // Do something with data
    }       
}

Both the action and the done() method are executed on the same thread, so they can effectively check the reference to whether there is an existing worker.

Note that effectively this is doing the same thing that allows a GUI to cancel an existing operation, except the cancel is done automatically when a new request is fired.

like image 84
Andrew Mao Avatar answered Oct 20 '22 00:10

Andrew Mao