реализация debounce в Java - PullRequest
       14

реализация debounce в Java

21 голосов
/ 20 января 2011

Для некоторого кода, который я пишу, я мог бы использовать хорошую общую реализацию debounce в Java.

public interface Callback {
  public void call(Object arg);
}

class Debouncer implements Callback {
    public Debouncer(Callback c, int interval) { ... }

    public void call(Object arg) { 
        // should forward calls with the same arguments to the callback c
        // but batch multiple calls inside `interval` to a single one
    }
}

Когда call() вызывается несколько раз в interval миллисекундах с одним и тем же аргументомфункция обратного вызова должна вызываться ровно один раз.

Визуализация:

Debouncer#call  xxx   x xxxxxxx        xxxxxxxxxxxxxxx
Callback#call      x           x                      x  (interval is 2)
  • (что-то вроде) это уже существует в некоторой стандартной библиотеке Java?
  • Как бы вы это реализовали?

Ответы [ 5 ]

26 голосов
/ 07 января 2014

Пожалуйста, обратите внимание на следующее решение для обеспечения безопасности потока.Обратите внимание, что степень детализации блокировки находится на уровне ключа, поэтому вызовы только на одном и том же ключе блокируют друг друга.Он также обрабатывает случай истечения срока действия ключа K, который происходит во время вызова call (K).

public class Debouncer <T> {
  private final ScheduledExecutorService sched = Executors.newScheduledThreadPool(1);
  private final ConcurrentHashMap<T, TimerTask> delayedMap = new ConcurrentHashMap<T, TimerTask>();
  private final Callback<T> callback;
  private final int interval;

  public Debouncer(Callback<T> c, int interval) { 
    this.callback = c;
    this.interval = interval;
  }

  public void call(T key) {
    TimerTask task = new TimerTask(key);

    TimerTask prev;
    do {
      prev = delayedMap.putIfAbsent(key, task);
      if (prev == null)
        sched.schedule(task, interval, TimeUnit.MILLISECONDS);
    } while (prev != null && !prev.extend()); // Exit only if new task was added to map, or existing task was extended successfully
  }

  public void terminate() {
    sched.shutdownNow();
  }

  // The task that wakes up when the wait time elapses
  private class TimerTask implements Runnable {
    private final T key;
    private long dueTime;    
    private final Object lock = new Object();

    public TimerTask(T key) {        
      this.key = key;
      extend();
    }

    public boolean extend() {
      synchronized (lock) {
        if (dueTime < 0) // Task has been shutdown
          return false;
        dueTime = System.currentTimeMillis() + interval;
        return true;
      }
    }

    public void run() {
      synchronized (lock) {
        long remaining = dueTime - System.currentTimeMillis();
        if (remaining > 0) { // Re-schedule task
          sched.schedule(this, remaining, TimeUnit.MILLISECONDS);
        } else { // Mark as terminated and invoke callback
          dueTime = -1;
          try {
            callback.call(key);
          } finally {
            delayedMap.remove(key);
          }
        }
      }
    }  
  }
10 голосов
/ 10 июля 2016

Вот моя реализация:

public class Debouncer {
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
    private final ConcurrentHashMap<Object, Future<?>> delayedMap = new ConcurrentHashMap<>();

    /**
     * Debounces {@code callable} by {@code delay}, i.e., schedules it to be executed after {@code delay},
     * or cancels its execution if the method is called with the same key within the {@code delay} again.
     */
    public void debounce(final Object key, final Runnable runnable, long delay, TimeUnit unit) {
        final Future<?> prev = delayedMap.put(key, scheduler.schedule(new Runnable() {
            @Override
            public void run() {
                try {
                    runnable.run();
                } finally {
                    delayedMap.remove(key);
                }
            }
        }, delay, unit));
        if (prev != null) {
            prev.cancel(true);
        }
    }

    public void shutdown() {
        scheduler.shutdownNow();
    }
}

Пример использования:

final Debouncer debouncer = new Debouncer();
debouncer.debounce(Void.class, new Runnable() {
    @Override public void run() {
        // ...
    }
}, 300, TimeUnit.MILLISECONDS);
3 голосов
/ 20 января 2011

Я не знаю, существует ли он, но это должно быть просто реализовать.

class Debouncer implements Callback {

  private CallBack c;
  private volatile long lastCalled;
  private int interval;

  public Debouncer(Callback c, int interval) {
     //init fields
  }

  public void call(Object arg) { 
      if( lastCalled + interval < System.currentTimeMillis() ) {
        lastCalled = System.currentTimeMillis();
        c.call( arg );
      } 
  }
}

Конечно, этот пример немного упрощает его, но это более или менее все, что вам нужно.Если вы хотите сохранить отдельные таймауты для разных аргументов, вам понадобится Map<Object,long> вместо просто long для отслеживания времени последнего выполнения.

1 голос
/ 15 сентября 2015

Следующая реализация работает в потоках на основе обработчика (например, в основном потоке пользовательского интерфейса или в IntentService).Ожидается, что он будет вызван только из потока, в котором он создан, и он также выполнит свое действие в этом потоке.

public class Debouncer
{
    private CountDownTimer debounceTimer;
    private Runnable pendingRunnable;

    public Debouncer() {

    }

    public void debounce(Runnable runnable, long delayMs) {
        pendingRunnable = runnable;
        cancelTimer();
        startTimer(delayMs);
    }

    public void cancel() {
        cancelTimer();
        pendingRunnable = null;
    }

    private void startTimer(final long updateIntervalMs) {

        if (updateIntervalMs > 0) {

            // Debounce timer
            debounceTimer = new CountDownTimer(updateIntervalMs, updateIntervalMs) {

                @Override
                public void onTick(long millisUntilFinished) {
                    // Do nothing
                }

                @Override
                public void onFinish() {
                    execute();
                }
            };
            debounceTimer.start();
        }
        else {

            // Do immediately
            execute();
        }
    }

    private void cancelTimer() {
        if (debounceTimer != null) {
            debounceTimer.cancel();
            debounceTimer = null;
        }
    }

    private void execute() {
        if (pendingRunnable != null) {
            pendingRunnable.run();
            pendingRunnable = null;
        }
    }
}
0 голосов
/ 20 января 2011

Похоже, это может сработать:

class Debouncer implements Callback {
    private Callback callback;
    private Map<Integer, Timer> scheduled = new HashMap<Integer, Timer>();
    private int delay;

    public Debouncer(Callback c, int delay) {
        this.callback = c;
        this.delay = delay;
    }

    public void call(final Object arg) {
        final int h = arg.hashCode();
        Timer task = scheduled.remove(h);
        if (task != null) { task.cancel(); }

        task = new Timer();
        scheduled.put(h, task);

        task.schedule(new TimerTask() {
            @Override
            public void run() {
                callback.call(arg);
                scheduled.remove(h);
            }
        }, this.delay);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...