2011-01-20 30 views
16

Đối với một số mã tôi đang viết tôi có thể sử dụng một thực hiện chung tốt đẹp của debounce trong Java.thực hiện debounce trong 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 
    } 
} 

Khi call() được gọi nhiều lần trong interval mili giây với lập luận tương tự chức năng gọi lại nên được gọi là đúng một lần.

Một hình dung:

Debouncer#call xxx x xxxxxxx  xxxxxxxxxxxxxxx 
Callback#call  x   x      x (interval is 2) 
  • Liệu (một cái gì đó tương tự) tồn tại này đã có trong một số thư viện chuẩn Java?
  • Bạn sẽ triển khai điều đó như thế nào?
+0

trông giống như [java.util.concurrency] (http://download.oracle.com/javase/1.5 .0/docs/api/java/util/concurrent/package-summary.html) cung cấp các khối xây dựng – levinalex

+2

Tôi biết đây là một câu hỏi cũ, nhưng tôi đã đăng một câu hỏi tương tự cách đây vài tháng ở đây: http: // stackoverflow. com/questions/18723112/canceling-method-calls-wh vi-cùng-phương pháp-được gọi là nhiều lần/18758408 # 18758408 và cung cấp triển khai có thể tái sử dụng trên GitHub có thể quan tâm – ARRG

Trả lời

19

Hãy xem xét các giải pháp đề an toàn sau đây. Lưu ý rằng độ chi tiết khóa ở cấp độ khóa, do đó chỉ có các cuộc gọi trên cùng một khối khóa. Nó cũng xử lý trường hợp hết hạn trên khóa K xảy ra trong khi gọi (K) được gọi.

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

cảm ơn bạn. thế này tốt hơn. – levinalex

+0

@levinalex: vừa sửa lỗi trong cuộc gọi (..). Vòng lặp đã được thêm vào để đảm bảo rằng chúng tôi không bao giờ có một tác vụ theo lịch trình không có trong bản đồ. –

+0

một cách thô lỗ, nhưng có thể thực hiện được: (https://github.com/rmalchow/debouncer-aspect/tree/develop) – rmalchow

4

Tôi không biết nó có tồn tại hay không nhưng cần phải thực hiện đơn giản.

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

Tất nhiên ví dụ này đơn giản hóa nó một chút, nhưng điều này ít nhiều là bạn cần. Nếu bạn muốn giữ thời gian chờ riêng biệt cho các đối số khác nhau, bạn sẽ cần một Map<Object,long> thay vì chỉ một long để theo dõi thời gian thực hiện cuối cùng.

+0

Điều tôi cần là ngược lại. cuộc gọi lại nên được gọi tại _end_ của tất cả các cuộc gọi. (Tôi muốn sử dụng nó để triển khai [this] (http://stackoverflow.com/questions/4742017/avoid-detecting-incomplete-files-when-watching-a-directory-for-changes-in-java)) dường như yêu cầu Threads/Timeouts – levinalex

+1

@levinalex Tôi vẫn nghĩ rằng bạn có thể làm cho nó hoạt động theo cách này, nhưng nếu bạn không, không sử dụng các chủ đề, sử dụng 'Timer' hoặc' ScheduledExecutorService' thay vào đó, nó sạch hơn và an toàn hơn theo cách đó. – biziclop

+0

Cảm ơn vì những điều này. Tôi đang cố gắng thực hiện công việc đó ngay bây giờ. (Tôi chưa bao giờ thực hiện đồng thời Java trước đây) – levinalex

0

này có vẻ như nó có thể làm việc:

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); 
    } 
} 
+1

Bạn có bao giờ thêm đối tượng vào bản đồ băm không?Ngoài ra, bạn không bao giờ nên sử dụng 'hashCode' làm khóa, vì nó sẽ dễ dàng tạo ra các va chạm. Chưa kể rằng các đối tượng thuộc loại khác nhau sẽ dễ dàng có mã băm bằng nhau, ngay cả khi hàm băm của chúng là hoàn hảo. – Groo

+0

cố định câu trả lời để thực sự lên lịch cho mọi thứ. Tôi sẽ sử dụng gì thay vì hashCode? – levinalex

+0

Chỉ cần sử dụng đối tượng thực tế làm khóa (I.e. 'Map '). 'HashMap' sau đó sử dụng mã băm của đối tượng trong nội bộ để nhanh chóng nhảy vào thùng chứa mục của bạn (và các mục khác có cùng mã băm), nhưng sau đó mã băm bị bỏ qua và nó so sánh thực tế' Object' với cái xô đó để tìm cái kết hợp. Tldr; bất cứ khi nào bạn gọi 'hashCode()' trong mã của bạn, bạn có thể làm điều gì đó sai. – Groo

1

Triển khai sau đây hoạt động trên các chuỗi dựa trên trình xử lý (ví dụ: chuỗi giao diện người dùng chính hoặc trong IntentService). Nó hy vọng chỉ được gọi từ thread mà nó được tạo ra, và nó cũng sẽ chạy nó trên action này.

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; 
     } 
    } 
} 
6

Dưới đây là thực hiện của tôi:

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

Ví dụ sử dụng:

final Debouncer debouncer = new Debouncer(); 
debouncer.debounce(Void.class, new Runnable() { 
    @Override public void run() { 
     // ... 
    } 
}, 300, TimeUnit.MILLISECONDS); 
Các vấn đề liên quan