かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Reactive Extensions再入門 その4「Timer系のファクトリメソッド」

2011/11/06 修正
TimerはHotだ(キリッ)と大嘘を書いてたので修正しましたm(_ _)m
id:neueccさんに感謝です。

時間と共に値を発行するIObservable

ここでは時間と共に値を発行するIObservableを返すファクトリメソッドを使って動作を確認します。

Observable.Timerメソッド

まず、一番直感的に理解に理解できると思われるTimerメソッドを使用します。Timerメソッドは名前の通り一定時間ごとに値を発行します。発行する値はTimerが実行された回数になります。いくつかオーバーロードがありますが、第一引数にタイマーを開始するまでの時間、第二引数にタイマーのインターバルをTimeSpan型で指定するオーバーロードが、一番使用頻度が高いと思います。コード例を下記に示します。

// 3秒後から1秒間隔で値を発行するIObservable<long>を作成する
var source = Observable.Timer(
    TimeSpan.FromSeconds(3), 
    TimeSpan.FromSeconds(1));
// 購読
var subscription = source.Subscribe(
    i => Console.WriteLine("OnNext({0})", i),
    ex => Console.WriteLine("OnError({0})", ex.Message),
    () => Console.WriteLine("Completed()"));

// 3秒後からOnNext(回数)が表示される
Console.WriteLine("please enter key...");
Console.ReadLine();
// Observableが発行する値の購読を停止
Console.WriteLine("dispose method call.");
subscription.Dispose();
// Disposeをすると値が発行されなくなる。
Console.WriteLine("please enter key...");
Console.ReadLine();

コメントにもある通り、上記のコードは3秒後から1秒間隔で値を発行するIObservableを作成してSubscribeで購読しています。そして、Console.ReadLineで待機しています。暫く待っているとOnNextが発行されます。適当なタイミングでEnterキーを押すとsubscription.Dispose()が実行され、購読が解除されます。Disposeのあとは、OnNextが実行されないことを確認できます。実行結果を下記に示します。

please enter key... // ここから3秒何も表示されない
OnNext(0)        // 3秒たつと1秒間隔でOnNextが呼ばれる
OnNext(1)
OnNext(2)
OnNext(3)
OnNext(4)
OnNext(5)

dispose method call. // Enterを押してDisposeが呼ばれるとOnNextも止まる
please enter key...

Timerの実行イメージを下図に示します。

Observable.Intervalメソッド

次もTimerメソッドと同じように使用できるメソッドを紹介します。こちらはIntervalメソッドでTimeSpanを1つ渡すだけでシンプルに使用できます。Subscribeした時点からTimeSpanで指定した間隔で値を発行します。発行する値はTimerと同じで何回値を発行したかという数字になります。コード例を下記に示します。

// 500ms間隔で値を発行する
var source = Observable.Interval(TimeSpan.FromMilliseconds(500));
// 購読
var subscription = source.Subscribe(
    i => Console.WriteLine("OnNext({0})", i),
    ex => Console.WriteLine("OnError({0})", ex.Message),
    () => Console.WriteLine("Completed()"));

Console.WriteLine("please enter key...");
Console.ReadLine();
// Observableが発行する値の購読を停止
Console.WriteLine("dispose method call.");
subscription.Dispose();
// Disposeをすると値が発行されても受け取らなくなる。
Console.WriteLine("please enter key...");
Console.ReadLine();

上記の例では500ms間隔で値を発行しています。Enterキーを押すとDisposeを読んで購読を停止して再度Enterキーが押されるまで待機します。実行結果を下記に示します。

please enter key...
OnNext(0)
OnNext(1)
OnNext(2)
OnNext(3)
OnNext(4)
OnNext(5)
OnNext(6)

dispose method call.
please enter key...

この実行結果では6が表示されたタイミングでEnterキーを押してDisposeを実行しています。実行結果からは読み取れませんが、上記の実行結果はDisposeが実行されたあとに数秒間待機して購読が停止していることを確認しています。

Observable.Generateメソッド

次は、以前にも使用したGenerateメソッドを使用します。GenerateメソッドにはTimeSpanを受け取るオーバーロードがあり、これを使うことで指定した時間間隔で値を発行するIObservableを作成できます。コード例を下記に示します。

var source = Observable.Generate(
    // 0から
    0,
    // i < 10以下の間繰り返す
    i => i < 10,
    // iは1ずつ増える
    i => ++i,
    // 発行する値はiの二乗
    i => i * i,
    // 値は(発行する値 * 100)ms間隔で発行する
    i => TimeSpan.FromMilliseconds(i * 100));

// 購読
var subscription = source.Subscribe(
    i => Console.WriteLine("OnNext({0})", i),
    ex => Console.WriteLine("OnError({0})", ex.Message),
    () => Console.WriteLine("Completed()"));

Console.WriteLine("please enter key...");
Console.ReadLine();
// Observableが発行する値の購読を停止
Console.WriteLine("dispose method call.");
subscription.Dispose();
// Disposeをすると値が発行されても受け取らなくなる。
Console.WriteLine("please enter key...");
Console.ReadLine();

上記の例は(0 * 0 * 100)ms, (1 * 1 * 100)ms, (2 * 2 * 100)ms, (3 * 3 * 100)ms…(9 * 9 * 100)msと値の発行回数増える度にインターバルを長くとるようにしています。実行結果を下記に示します。

please enter key...
OnNext(0)
OnNext(1)
OnNext(4)
OnNext(9)
OnNext(16)
OnNext(25)
OnNext(36)
OnNext(49)
OnNext(64)
OnNext(81)
Completed()

dispose method call.
please enter key...

上記実行例は、最後までEnterキーを押さずに実行した場合になります。途中でEnterキーを押した場合の実行例を下記に示します。

please enter key...
OnNext(0)
OnNext(1)
OnNext(4)
OnNext(9)
OnNext(16)

dispose method call.
please enter key...

このようにDisposeを呼ぶとGenerateメソッドの処理を途中から購読しなくなります。

次回予告?

後1回か2回くらいファクトリ系メソッドをやったあとに、ちょっと面白い(と個人的に思ってる)ところに入っていこうと思います。正直たんたんと値発行しては表示するだけのサンプルに飽きてきました!でもあと2回やります。