Smart asynchronous redrawing in Java

I have a use case based on a GUI problem that I would like to convey to your insight.

Use case

I have a graphical interface that displays the calculation result depending on some parameters set by the user in the graphical interface. For example, when a user moves the slider, several events are triggered that trigger a new calculation. When the user adjusts the value of the slider from A to B, a dozen events are triggered.

But the calculation can take up to several seconds, while adjusting the slider can trigger an event every few seconds.

How to write the correct thread that will listen to these events, and how to filter them so that the redrawing of the results is live? Ideally, you would like something like

  • start a new calculation as soon as the first change event is received;
  • cancel the first calculation if a new event is received, and start a new one with new parameters;
  • but make sure that the last event will not be lost, because the last completed calculation should be the last with the last updated parameters.

What i tried

My friend (A. Cardona) proposed this low-level approach in the Updater thread, which prevents too many events from causing a calculation. I will copy 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 ... } 

Each time an event is dispatched by the GUI, indicating that the parameters are changed, we call updater.doUpdate() . This causes the refresh() method to be called much less. But I do not control it.

Another way?

I was wondering if there is another way to do this to use the jaca.concurrent classes. But I could not sort within Executors, where should I start.

Do any of you have experience with similar uses?

thanks

+7
source share
5 answers

If you use Swing , SwingWorker provides options for this, and you do not have to deal with a thread pool.

Redeem a SwingWorker for each request. If a new request arrives and the worker is not completed, you can cancel() it and just run the new SwingWorker . As for what another poster said, I don’t think publish() and process() are what you are looking for (although they are also very useful), as they are for the case when an employee can accelerate events faster than GUI can handle 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 in one thread, so they can effectively check the link for the existence of an existing worker.

Note that this effectively does the same thing that allows the GUI to undo an existing operation, except that undoing is performed automatically when a new request is launched.

+4
source

I would provide an additional degree of disconnection between the GUI and the controls using the queue.

If you use BlockingQueue between two processes. Whenever the controls change, you can publish new settings to the queue.

Your graphics component can read the queue whenever it wants, and act on upcoming events or discard them as necessary.

+1
source

I would look at SwingWorker.publish () ( http://docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html )

Publish allows the background thread of a SwingWorker to call process () calls, but not every publish () call calls process (). If multiple process calls are returned before a process () call, and SwingWorker combines the parameters used for several publish calls into a single call for processing.

I had a progress dialog in which the processed files were displayed; files were processed faster than the user interface could keep up with them, and I did not want the processing to slow down to display file names; I used this and displayed the process only the final file name sent to the process (); all I wanted in this case was to tell the user where the current processing was, they still won’t read all the file names. My user interface worked very smoothly with this.

+1
source

Take a look at the implementation of javax.swing.SwingWorker (source code in the Java JDK), with an emphasis on linking two methods: publishing and processing.

They will not be directly applicable, as they are, to your problem - however, they demonstrate how you can post (publish) updates in the workflow and then serve them in the workflow (process).

Since you only need the last job request, you don’t even need a queue for your situation: keep only the last job request. The sample, which is the "last request" for some short period (1 second), so as not to stop / restart many times in 1 second, and if it has changed, stop working and restart.


The reason you don't want to use the as-is publication / process is because the process always starts in the Swing Event send thread - not at all suitable for lengthy calculations.

+1
source

The key point here is that you want to be able to cancel the current calculation. A calculation must frequently check a condition to see if it needs to be interrupted.

 volatile Param newParam; Result compute(Param param) { loop compute a small sub problem if(newParam!=null) // abort return null; return result } 

Passing a parameter from an event stream to calculate a stream

 synchronized void put(Param param) // invoked by event thread newParam = param; notify(); synchronized Param take() while(newParam==null) wait(); Param param = newParam; newParam=null; return param; 

And the flow of computing does

 public void run() while(true) Param param = take(); Result result = compute(param); if(result!=null) paint result in event thread 
0
source

All Articles