Debounce
1. 概要
Debounce は、ソースシーケンスから値を受け取るたびにタイマーをリセットし、指定した時間が経過しても新しい値が届かなかった場合にのみ、最後に受け取った値を下流に発行するオペレーターです。
高頻度で連続する値の中から「落ち着いた後の最終値」だけを取り出したい場合に適しています。典型的なユースケースは、テキスト入力フィールドのオートコンプリートや検索候補の取得です。
2. シグネチャ
TimeSpan によるデバウンス
csharp
public static Observable<T> Debounce<T>(
this Observable<T> source,
TimeSpan timeSpan)値を受け取るたびに timeSpan のタイマーを開始(リセット)し、タイマーが満了したら最後の値を発行します。
csharp
source.Debounce(TimeSpan.FromMilliseconds(300))TimeSpan + TimeProvider によるデバウンス
csharp
public static Observable<T> Debounce<T>(
this Observable<T> source,
TimeSpan timeSpan,
TimeProvider timeProvider)TimeProvider を指定してタイマーの実装を差し替えられます。テスト時に FakeTimeProvider を使用して時間を手動制御できます。
csharp
source.Debounce(TimeSpan.FromMilliseconds(300), fakeTimeProvider)非同期セレクターによるデバウンス
csharp
public static Observable<T> Debounce<T>(
this Observable<T> source,
Func<T, CancellationToken, ValueTask> throttleDurationSelector,
bool configureAwait = true)値ごとに異なるデバウンス期間を非同期関数で指定できます。関数が完了する前に新しい値が届くと、前の非同期処理はキャンセルされ、新しい値で再度関数が呼び出されます。
csharp
// 値に応じてデバウンス時間を変える
source.Debounce(async (value, ct) =>
{
var delay = value > 100
? TimeSpan.FromMilliseconds(500)
: TimeSpan.FromMilliseconds(200);
await Task.Delay(delay, ct);
})overload の使い分け
| overload | 使う場面 |
|---|---|
TimeSpan | 固定のデバウンス時間で十分な場合(最も一般的) |
TimeSpan, TimeProvider | ユニットテストで時間を制御したい場合 |
Func<T, CancellationToken, ValueTask> | 値に応じてデバウンス時間を動的に変えたい場合 |
3. マーブルダイアグラム
ソースが値を発行するたびにタイマーがリセットされます。タイマーが満了するまで新しい値が届かなかった場合にのみ、最後の値が下流に発行されます。連続して値が届いている間は何も発行されません。
4. サンプルコード
csharp
using R3;
// テキスト入力のオートコンプリート
searchTextObservable
.Debounce(TimeSpan.FromMilliseconds(300))
.SubscribeAwait(async (query, ct) =>
{
var results = await SearchAsync(query, ct);
UpdateSuggestions(results);
});
// 値に応じた動的デバウンス
sensorDataObservable
.Debounce(async (value, ct) =>
{
// 急激な変化には短いデバウンス、緩やかな変化には長いデバウンスを適用
var delay = Math.Abs(value.Delta) > threshold
? TimeSpan.FromMilliseconds(100)
: TimeSpan.FromMilliseconds(500);
await Task.Delay(delay, ct);
})
.Subscribe(value => ProcessStableValue(value));
// FakeTimeProvider を使ったテスト
var fakeTime = new FakeTimeProvider();
var list = new List<int>();
Observable.Create<int>(observer =>
{
observer.OnNext(1);
observer.OnNext(2);
observer.OnNext(3);
return Disposable.Empty;
})
.Debounce(TimeSpan.FromSeconds(1), fakeTime)
.Subscribe(x => list.Add(x));
fakeTime.Advance(TimeSpan.FromSeconds(1));
// list: [3] — 最後の値のみ発行される5. 補足
ThrottleFirst / ThrottleLast / ThrottleFirstLast との違い
| オペレーター | 動作 | 発行タイミング |
|---|---|---|
| Debounce | 値が来なくなるまで待つ | 値が落ち着いた後 |
| ThrottleFirst | 最初の値を即座に発行し、一定期間無視 | 最初の値が来た直後 |
| ThrottleLast | 一定期間の最後の値を発行 | 期間の終了時 |
| ThrottleFirstLast | 最初の値 + 期間終了時の最後の値 | 即座 + 期間終了時 |
Debounce は値が連続して発行される状態が落ち着くのを待つ動作であり、ThrottleFirst / ThrottleLast は一定間隔でサンプリングする動作です。入力が途切れない場合、Debounce は値を発行しない可能性がありますが、ThrottleLast は定期的に発行します。
フレームベースの版は DebounceFrame を参照してください。