かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Reactive Extensions再入門 その27「時間でフィルタリング?Sampleメソッド」

過去記事インデックス

はじめに

また、しばらく間があいていましたがマイペースでいきます。

Sampleメソッド

ここでは、Sampleメソッドについて説明します。Sampleメソッドは、指定した間隔(時間や任意のタイミング)で最後に発行された値を後続に流すメソッドになります。大量の値が発行されるようなIObservableのシーケンスから任意の間隔で値を絞って後続の処理に渡すケースなどで利用できます。
メソッドのシグネチャを下記に示します。

public static IObservable<T> Sample<T>(
  this IObservable<T> source, 
  TimeSpan interval);

このオーバーロードは、時間で値をフィルタリングします。もう1つ任意の間隔で後続に最後の値を流すオーバーロードは下記のようなシグネチャになっています。

public static IObservable<T> Sample<T, TSample>(
  this IObservable<T> source, 
  IObservable<TSample> sampler);

このオーバーロードは、IObservableのOnNextで値が発行されるタイミングで後続に値を流します。このオーバーロードを使うことで、非同期処理が終わったタイミングや、ボタンのクリックイベントなどをトリガーにして、一番最後に発行された値を後続に流せます。
コード例を以下に示します。

var r = new Random();
var subscriber = Observable
    // 100msの間隔で
    .Interval(TimeSpan.FromMilliseconds(100))
    // 0〜99の乱数を発生させる
    .Select(_ => r.Next(100))
    // 値が発行されたことを確認するためのダンプ
    .Do(i => Console.WriteLine("{0:HH:mm:ss} Dump {1}", DateTime.Now, i))
    // 1秒間隔で最後に発行された値を後続に流す
    .Sample(TimeSpan.FromMilliseconds(1000))
    // 購読
    .Subscribe(
        // 値を表示
        i => Console.WriteLine("{0:HH:mm:ss} OnNext {1}", DateTime.Now, i));
Console.WriteLine("Please enter key");
Console.ReadLine();
// 購読終了
subscriber.Dispose();

100ミリ秒間隔で0〜99の値を発行しています。この値をSampleメソッドを使って1秒間隔で最後に発行された値に絞り込んでSubscribeで結果を表示しています。実行結果を以下に示します。

Please enter key
23:13:23 Dump 63
23:13:23 Dump 0
23:13:23 Dump 36
23:13:23 Dump 18
23:13:23 Dump 53
23:13:24 Dump 65
23:13:24 Dump 63
23:13:24 Dump 95
23:13:24 Dump 39
23:13:24 Dump 44
23:13:24 OnNext 44 <- ここ
23:13:24 Dump 72
23:13:24 Dump 91
23:13:24 Dump 36
23:13:24 Dump 37
23:13:24 Dump 68
23:13:25 Dump 69
23:13:25 Dump 98
23:13:25 Dump 67
23:13:25 Dump 79
23:13:25 Dump 23
23:13:25 OnNext 23 <- ここ
23:13:25 Dump 6
23:13:25 Dump 53
23:13:25 Dump 22
23:13:25 Dump 47
23:13:25 Dump 94
23:13:26 Dump 67
23:13:26 Dump 99
23:13:26 Dump 2
23:13:26 Dump 70
23:13:26 Dump 76
23:13:26 OnNext 70 <- ここ
23:13:26 Dump 82

長い実行結果になりますが、Dumpが大量に表示されていることから100msごとに乱数が発生していることが確認できます。その中でSubscribeのOnNextまで渡っている値(実行結果の中で <-ここ で示している箇所)は1秒間隔になっていることが確認できます。
上記の例では、発行される値の間隔に比べてSampleで取得する値をフィルタリングするように設定していましたが、逆に値は10秒間隔で発行されるのに対してSampleメソッドで8秒でフィルタリングしたときに、どのような挙動になるのかを確認したいと思います。コード例を下記に示します。

var r = new Random();
var subscriber = Observable
    // 10秒間隔で
    .Interval(TimeSpan.FromSeconds(10))
    // 乱数を発生させる
    .Select(_ => r.Next(100))
    // 発生した乱数を表示
    .Do(i => Console.WriteLine("{0:HH:mm:ss} Dump {1}", DateTime.Now, i))
    // 8秒間隔でフィルタリングする
    .Sample(TimeSpan.FromSeconds(8))
    .Subscribe(
        // 渡ってきた値を表示する
        i => Console.WriteLine("{0:HH:mm:ss} OnNext {1}", DateTime.Now, i));

Console.WriteLine("Please enter key");
Console.ReadLine();
// 購読解除
subscriber.Dispose();

実行結果は以下のようになります。

Please enter key
23:23:20 Dump 1
23:23:20 OnNext 1
23:23:22 Dump 39
23:23:22 OnNext 39
23:23:24 Dump 91
23:23:24 OnNext 91
23:23:26 Dump 61
23:23:26 OnNext 61

結果からわかるように、Sampleメソッドは、8秒待っても値が発行されない(元のシーケンスは10秒間隔での値の発行のため)ので、値が発行されなかった場合は何もせず次のタイミングを待っています、次のタイミングで値がきていたら後続に値を流す動きをします。
IObservableを渡して任意のタイミングで、後続に値を流すメソッドのオーバーロードについては、割愛します。