2011-01-20 30 views
16

いくつかのコードについては、Javaでdebounceという素晴らしい一般的な実装を使用できます。Javaでデバウンスを実装する

call()は、コールバック関数は1回だけ呼び出されるべき同じ引数で intervalミリ秒単位で複数回呼び出され
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 
    } 
} 

可視化:

Debouncer#call xxx x xxxxxxx  xxxxxxxxxxxxxxx 
Callback#call  x   x      x (interval is 2) 
  • ん(のようなもの)これは、いくつかのJavaの標準ライブラリに既に存在していますか?
  • これはどのように実装しますか?
+0

は[java.util.concurrency](http://download.oracle.com/javase/1.5のように見えます.0/docs/api/java/util/concurrent/package-summary.html)はビルディングブロックを提供します – levinalex

+2

これは古い質問ですが、私は数ヶ月前にここで同様の質問を投稿しました:http:// stackoverflow。 co.jp/questions/18723112/canceling-method-calls-wh同じメソッドを複数の時刻/ 18758408#18758408と呼び、興味のあるGitHubで再利用可能な実装を提供しました – ARRG

答えて

19

は次のスレッド安全なソリューションをご検討ください。ロックの細分性はキーレベルにあり、同じキー上のコールだけが互いにブロックすることに注意してください。また、コール(K)が呼び出されている間に発生するキーKの有効期限のケースも処理します。

+0

ありがとうございます。これの方が良い。 – levinalex

+0

@levinalex:コール(..)の不具合を修正しました。ループが追加され、マップにないスケジュールされたタスクが決してないことが確認されました。 –

+0

はおおよそですが、それにもかかわらず使用可能な実装です:(https://github.com/rmalchow/debouncer-aspect/tree/develop) – rmalchow

4

私はそれが存在するかどうかわかりませんが、実装するのは簡単なはずです。

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

もちろん、この例はあまり単純すぎますが、これは多かれ少なかれ必要なものです。異なる引数に対して別々のタイムアウトを保ちたい場合は、最後の実行時間を記録するのにlongの代わりにMap<Object,long>が必要です。

+0

私が必要とするのは逆です。コールバックはすべての呼び出しの_end_で呼び出される必要があります。 (私はそれを実装するために使用したい[this](http://stackoverflow.com/questions/4742017/avoid-detecting-incomplete-files-when-watching-a-directory-for-changes-in-java))スレッド/タイムアウトを必要とするようです。 – levinalex

+1

@levinalex私はまだこの方法で動作させることができると思っていますが、スレッドを使用しないで 'Timer'または' ScheduledExecutorService'を使用してください。 – biziclop

+0

ありがとうございます。私は今、その仕事をしようとしています。 (私は以前にJavaの並行処理を行ったことがありません) – levinalex

0

これは、仕事ができるようになります:

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

オブジェクトをハッシュマップに追加したことはありますか?また、簡単に衝突を作成するため、キーとして 'hashCode'を使用しないでください。たとえ独自のハッシュ関数が完璧であっても、異なる型のオブジェクトは簡単に等しいハッシュコードを持つことは言うまでもありません。 – Groo

+0

は、事実をスケジュールするための答えを修正しました。 hashCodeの代わりに私は何を使うでしょうか? – levinalex

+0

単に実際のオブジェクトをキーとして使用してください(つまり、Map )。 'HashMap'はオブジェクトのハッシュコードを内部的に使用してアイテム(および同じハッシュコードを持つ潜在的に他のアイテム)を含むバケットに素早くジャンプしますが、その後はハッシュコードは無視され、実際の' Object'とそのバケットを使って一致するものを探します。 Tldr;あなたのコードで 'hashCode()'を呼び出すたびに、何か間違ったことが起こりそうです。 – Groo

1

次の実装は、ハンドラベースのスレッド(メインUIスレッドや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; 
     } 
    } 
} 
6

ここに私の実装です:

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); 
関連する問題