Lightweight postDelayed / removeCallbacks

J

Jan Burse

Dear All,

Just fidling around with a Android / Swing port.
Just wonder whether somebody has worked on an
untility to provide the following under Swing
which is inspired by the Android View(*):

postDelayed(Runnable r, int d):
Posts an event on the EDT, which will
invoked r after a delay of d millisecond.

removeCallbacks(Runnable r):
Immediately remove all events from the
EDT that would invoke r.

One could use javax.swing.Timer for the first
method. Something along:

public void postDelayed(final Runnable r, int d) {
final Timer t=new Timer(d,new ActionListener() {
public void actionPerformed(ActionEvent e) {
r.run();
}
});
t.setRepeats(false);
t.start();
}

But then for the second method one would need to
track the created timers, so as to be able to
selectively stop them.

Anybody already implemented a corresponding utility
in the spirit of SwingUtil.invokeLater/invokeAndWait?
Would be glad to receive hint so as not to reinvent
the wheel.

Bye

(*)
http://developer.android.com/reference/android/view/View.html
 
S

Steven Simpson

postDelayed(Runnable r, int d):
Posts an event on the EDT, which will
invoked r after a delay of d millisecond.

removeCallbacks(Runnable r):
Immediately remove all events from the
EDT that would invoke r.

One could use javax.swing.Timer for the first
method. Something along:

public void postDelayed(final Runnable r, int d) {
final Timer t=new Timer(d,new ActionListener() {
public void actionPerformed(ActionEvent e) {
r.run();
}
});
t.setRepeats(false);
t.start();
}

But then for the second method one would need to
track the created timers, so as to be able to
selectively stop them.

A first stab:

Keep a WeakHashMap<Runnable,Collection<Reference<Timer>>>, making all
changes on the EDT. Store weak references to each created Timer, in a
HashSet per Runnable.

When asked to remove all callbacks, remove the Runnable from the map,
get its Collection of Timers, and stop them. If the references have
already expired, you don't care. When all the Timers using the same
Runnable have expired, assuming that they naturally decay, the map entry
will be removed anyway.

// uncompiled
void removeCallbacks(final Runnable r) {
invokeAndWait(new Runnable() {
public void run() {
Collection<Reference<Timer>> timers = map.remove(r);
if (timers == null) return;
for (Reference<Timer> rt : timers) {
Timer t = rt.get();
if (t != null) t.stop();
}
}
});
}

void postDelayed(final Runnable r, int d) {
final Timer t=new Timer(d,new ActionListener() {
public void actionPerformed(ActionEvent e) {
r.run();
}
});
invokeAndWait(new Runnable() {
public void run() {
Collection<Reference<Timer>> coll = map.get(r);
if (coll == null) {
coll = new HashSet<Reference<Timer>>();
map.put(r, coll);
}
coll.add(new WeakReference<Timer>(t));
}
});
// should use d too!
t.setRepeats(false);
t.start();
}
 
J

Jan Burse

Steven said:
A first stab:

Thank you very much for your solution. Since I
was in need of a solution I implemented already
something yesterday.

Difference to your stab: postDelayed() and
removeCallbacks() do not wait for mouse /
keyevents / etc.. to flush. The problem
is that invokeAndWait() calls postEvent(),
which calls flushPendingEvents() in turn.

The Android removeCallbacks() and postDelayed()
are also immediate. They call in turn
removeMessages() respectively enqueueMessage()
which are immediate.

I didn't use javax.swing.Timer since I still
think they are not leightweight enough. So
my waiting is done via Object.wait(), whereas
the Swing timer uses Condition.awaitNanos().
Not sure what is more reliable.

I also use System.currentTimeMillis() whereas
the Swing timer uses System.nanoTime(). nanoTime()
is more stable, it does not change when the
wall clock is changed on the computer.

But current solution is then to use tail
recursion for a looping animation. Which
can get jerky, since the rate will be not
fixed. The delivery of the event might still
take some time before a next postDelayed()
happens, since it is delivered via postEvent()
which uses flushPendingEvents(). But this is
tollerated in the current application.

Could use a post with runnable = null to stop
the thread, something similar is done in the
Android looper. But currently the thread has to
be started via start(), and can then be easily
terminated via interrupt().

---------------- Begin Code ------------------

public class ThreadTimer extends Thread {
private final ArrayList<TimerEntry> entrylist =
new ArrayList<TimerEntry>();
private final Object entrylock = new Object();

public void postDelayed(Runnable r, int d) {
TimerEntry entry = new TimerEntry(r,
System.currentTimeMillis() + d);
synchronized (entrylock) {
for (int i = 0; i < entrylist.size(); i++) {
TimerEntry entry2 = entrylist.get(i);
if (entry.getWhen() < entry2.getWhen()) {
entrylist.add(i, entry);
entrylock.notifyAll();
return;
}
}
entrylist.add(entry);
entrylock.notifyAll();
}
}

public void removeCallbacks(Runnable r) {
synchronized (entrylock) {
int backsize = entrylist.size();
for (int i = entrylist.size() - 1; i >= 0; i--) {
TimerEntry entry = entrylist.get(i);
if (entry.getRunnable() == r)
entrylist.remove(i);
}
if (backsize != entrylist.size())
entrylock.notifyAll();
}
}

private TimerEntry take() throws InterruptedException {
for (; ; ) {
synchronized (entrylock) {
if (entrylist.size() == 0) {
entrylock.wait();
} else {
TimerEntry entry = entrylist.get(0);
long now = System.currentTimeMillis();
if (entry.getWhen() <= now) {
entrylist.remove(0);
return entry;
} else {
entrylock.wait(entry.getWhen() - now);
}
}
}
}
}

public void run() {
try {
for (; ; ) {
TimerEntry entry = take();
SwingUtilities.invokeLater(entry.getRunnable());
}
} catch (InterruptedException x) {
/* */
}
}

}

class TimerEntry {
private final Runnable runnable;
private final long when;

TimerEntry(Runnable r, long w) {
runnable = r;
when = w;
}

Runnable getRunnable() {
return runnable;
}

long getWhen() {
return when;
}

}

---------------- End Code --------------------
 
J

Jan Burse

Jan said:
---------------- Begin Code ------------------

Correction:

private TimerEntry take() throws InterruptedException {
synchronized (entrylock) {
for (; ; ) {

Since wait() and wait(int) anyway kind of
release the lock. See also the DelayQueue
implementation in java.util.concurrent.

Bye
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,073
Latest member
DarinCeden

Latest Threads

Top