2016-07-14 3 views
3

以下の簡単なプログラムを考えてみましょう。これは整数の観測可能性を持ち、直近に公表された整数が偶数か奇数かを計算する関数を持っています。予期しないことに、プログラムは番号が変更されたことを報告する前に最新の番号が偶数か奇数かを報告します。関数の入出力をどのように相関させるか?

static void Main(string[] args) { 
    int version = 0; 
    var numbers = new Subject<int>(); 
    IObservable<bool> isNumberEven = numbers.Select(i => i % 2 == 0); 
    isNumberEven 
     .Select(i => new { IsEven = i, Version = Interlocked.Increment(ref version) }) 
     .Subscribe(i => Console.WriteLine($"Time {i.Version} : {i.IsEven}")); 
    numbers 
     .Select(i => new { Number = i, Version = Interlocked.Increment(ref version) }) 
     .Subscribe(i => Console.WriteLine($"Time {i.Version} : {i.Number}")); 
    numbers.OnNext(1); 
    numbers.OnNext(2); 
    numbers.OnNext(3); 
    Console.ReadLine(); 
} 

出力は次のようになります。

Time 1 : False 
Time 2 : 1 
Time 3 : True 
Time 4 : 2 
Time 5 : False 
Time 6 : 3 

私は数を変更すると下流効果のカスケードをオフに設定しますと、これらは、彼らが起こるために報告されるだろうと思いました。サブスクリプション注文を交換すると、結果が報告される方法が入れ替わります。私はrxは非同期であり、物事が非決定的な順序で起こる可能性があることを理解しています。私の機能で.Delay()やWeb呼び出しを使用した場合、結果がいつ報告されるかわかりません。しかし、この状況では、私は非常に驚いています。

なぜこれは大きな問題ですか?これは、関数の入力と出力を関連づけようとすると(印刷番号が奇数か奇数かにかかわらず)、出力結果に入力パラメータを含める必要があります。

var isNumberEven = numbers.Select(i => new { 
    Number = i, 
    IsEven = i % 2 == 0 
}); 

私は、小さな単純な関数の束を構築し、rx演算子を使って洗練された計算を行うことができると考えました。しかし、結果を結合/結合/相関させるためにrx演算子を使用することはできません。私は各機能を定義するときに入力と出力を関連付ける必要があります。

場合によっては、rx演算子を使用して結果を関連付けることができます。すべての入力が出力を生成したら、私はその2つを圧縮することができます。しかし、あなたが入力を調整するようなことをするとすぐに、それはもはや機能しません。

このバージョンのプログラムは、数値が妥当な方法で偶数であるか奇数であるかを報告しているようです。

static void Main(string[] args) { 
    var numbers = new Subject<int>(); 
    var isNumberEven = numbers.Select(i => i % 2 == 0); 
    var publishedNumbers = numbers.Publish().RefCount(); 
    var report = 
     publishedNumbers 
     .GroupJoin(
      isNumberEven, 
      (_) => publishedNumbers, 
      (_) => Observable.Empty<bool>(), 
      (n, e) => new { Number = n, IsEven = e }) 
     .SelectMany(i => i.IsEven.Select(j => new { Number = i.Number, IsEven = j })); 
    report.Subscribe(i => Console.WriteLine($"{i.Number} {(i.IsEven ? "even" : "odd")}")); 
    numbers.OnNext(1); 
    numbers.OnNext(2); 
    numbers.OnNext(3); 
    Console.ReadLine(); 
} 

出力は次のようになります。

1 odd 
2 even 
3 odd 

しかし、これはラッキー偶然だったか、私はそれに頼ることができるかどうか私は知りません。 Rxのどのような操作が決定的な順序で行われますか?どれが予測できないのですか?結果に入力パラメータを含めるためにすべての関数を定義する必要がありますか?

答えて

3

あなたの最初のプログラムは、私が期待していたのと全く同じ振る舞いをしています。

私はrxが非同期であり、物事が非決定的な順序で起こる可能性があることを理解しています。

非決定論的な動作(並行性/スケジューリングなど)を導入すると、非確定的な順序でしか起こりません。そうでなければ、Rxは確定的です。

ここにはいくつかの問題や誤解があります。 1)変更可能な外部状態 - version 2)サブジェクトの使用(このサンプルでは実際には問題はありません) 3)コールバックの発行方法に関する誤解。

3)に注目してください。あなたがコードを取り出してそれを基本的なコールサイトにラップすると、Rxがどのようにシンプルになっているかが分かります。

numbers.OnNext(1);サブジェクトはサブスクリプションを検索し、サブスクリプションはサブスクライブした順にOnNextを検索します。

IObservable<bool> isNumberEven = numbers.Select(i => i % 2 == 0); 
isNumberEven 
    .Select(i => new { IsEven = i, Version = Interlocked.Increment(ref version) }) 
    .Subscribe(i => Console.WriteLine($"Time {i.Version} : {i.IsEven}")); 

isNumberEvenとしてあなたがこれにそれを減らす必要があることを、他の場所で使用されることはありませんことを、

numbers.Select(i => i % 2 == 0) 
    .Select(i => new { IsEven = i, Version = Interlocked.Increment(ref version) }) 
    .Subscribe(i => Console.WriteLine($"Time {i.Version} : {i.IsEven}")); 

一つが主張する可能性を減少させることができます。

私たちには最初の加入者がいることがわかります。 かつ効果的にそれが実行するコードは、この

private void HandleOnNext(int i) 
{ 
    var isEven = i % 2 == 0 
    var temp = new { IsEven = isEven , Version = Interlocked.Increment(ref version) }; 
    Console.WriteLine($"Time {temp .Version} : {temp .IsEven}"); 
} 

である私達の第2の加入者は、(.Subscribe(メソッドが偶数のサブスクリプションの後に呼ばれたので)、numbers加入者です。 彼のコードが効果的にあなたができる、あなたは完全にコードを解体したら、すべてのコールバックとサブスクリプションが具体化されたら、あなたは基本的にこの

void Main() 
{ 
    int version = 0; 

    //numbers.OnNext(1); 
    ProcessEven(1, ref version); 
    ProcessNumber(1, ref version); 
    //numbers.OnNext(2); 
    ProcessEven(2, ref version); 
    ProcessNumber(2, ref version); 
    //numbers.OnNext(3); 
    ProcessEven(3, ref version); 
    ProcessNumber(3, ref version); 
} 

// Define other methods and classes here 
private void ProcessEven(int i, ref int version) 
{ 
    var isEven = i % 2 == 0; 
    var temp = new { IsEven = isEven, Version = Interlocked.Increment(ref version) }; 
    Console.WriteLine($"Time {temp.Version} : {temp.IsEven}"); 
} 
private void ProcessNumber(int i, ref int version) 
{ 
    var temp = new { Number = i, Version = Interlocked.Increment(ref version) }; 
    Console.WriteLine($"Time {temp.Version} : {temp.Number}"); 
} 

で終わるので

private void HandleOnNext(int i) 
{ 
    var temp = new { Number = i, Version = Interlocked.Increment(ref version) }; 
    Console.WriteLine($"Time {temp.Version} : {temp.Number}"); 
} 

に煮詰めすることができますこれは起こる魔法ではなく、すべてが決定論的であることを見てください。

入力パラメータを結果に含めるために、すべての関数を定義する必要がありますか?

あなたの質問(私はRxの誤解を躊躇しています)に答えるには、結果シーケンスの順序が非決定論的である場合にのみ行う必要があります。 これは、一度に複数のWebリクエストを発行した場合の例です。 あなたが送信した順番ですべて応答するかどうかはわかりません。 しかし、これらのシナリオは、Concat

+1

などのような演算子を使用して強制的に強制的に実行することができます。私は今日あなたの本の為に99セントをアマゾンで支払った。 – JustinM

+0

ありがとう!私は大量の利益がウェブサイトの運営をほぼカバーしていることを保証します。 –

関連する問題