.NET Reactive Extensionsでサブジェクトが推奨されないのはなぜですか?


111

私は現在、.NETのReactive Extensionsフレームワークに慣れつつあり、私が見つけたさまざまな導入リソース(主にhttp://www.introtorx.com)に取り組んでいます

私たちのアプリケーションには、ネットワークフレームを検出するいくつかのハードウェアインターフェイスが含まれます。これらはIObservableになります。次に、これらのフレームを使用したり、データに対して何らかの変換を実行して新しいタイプのフレームを生成したりするさまざまなコンポーネントがあります。たとえば、n番目のフレームごとに表示する必要がある他のコンポーネントもあります。Rxがアプリケーションに役立つと確信していますが、IObserverインターフェイスの実装の詳細に苦労しています。

私が読んでいたリソースのほとんど(すべてではないにしても)は、IObservableインターフェイスを自分で実装するのではなく、提供された関数またはクラスの1つを使用するべきだと述べています。私の研究から、を作成するSubject<IBaseFrame>と必要なものが提供されるように見えSubject<IBaseFrame>ます。ハードウェアインターフェイスからデータを読み取り、インスタンスのOnNext関数を呼び出す単一のスレッドが必要です。異なるIObserverコンポーネントは、そのサブジェクトから通知を受け取ります。

私の混乱は、このチュートリアルの付録に書かれているアドバイスから来ています:

サブジェクトタイプの使用は避けてください。Rxは事実上、関数型プログラミングのパラダイムです。サブジェクトを使用するということは、状態を管理していることを意味します。状態の変化と非同期プログラミングの両方を同時に処理することは、正しく行うのが非常に困難です。さらに、多くの演算子(拡張メソッド)は、サブスクリプションとシーケンスの正確で一貫したライフタイムが維持されるように注意深く書かれています。主題を紹介すると、これを壊すことができます。サブジェクトを明示的に使用すると、将来のリリースでもパフォーマンスが大幅に低下する可能性があります。

私のアプリケーションはパフォーマンスが非常に重要です。Rxパターンを使用してパフォーマンスをテストしてから、本番用コードに移行します。ただし、Subjectクラスを使用してRxフレームワークの精神に反することを行っていること、およびフレームワークの将来のバージョンがパフォーマンスを低下させることを心配しています。

私がやりたいことを行うより良い方法はありますか?ハードウェアポーリングスレッドは、オブザーバーの有無に関係なく継続的に実行されるため(HWバッファーはそれ以外の場合にバックアップされます)、これは非常にホットなシーケンスです。次に、受信したフレームを複数のオブザーバーに渡す必要があります。

どんなアドバイスもいただければ幸いです。


1
それは主題の理解に本当に役立ちました。アプリケーションでの使用方法について、頭の中で真っ直ぐに考えています。私はそれらが正しいことを知っています-私は非常にプッシュ指向のコンポーネントのパイプラインを持っています、そしてGUIで表示するためにUIスレッドであらゆる種類のフィルタリングと呼び出しを行う必要があるだけでなく、最後に受信したフレームなどをバッファリングする必要があります等-私は私がそれを最初に正しく行うことを確認する必要があるだけです!
アンソニー

回答:


70

OK、私たちの独断的な方法を無視し、すべての「主体は良い/悪い」を無視するなら。問題空間を見てみましょう。

私はあなたがどちらかを理解する必要があるシステムの2つのスタイルのうちの1つを持っているに違いない。

  1. メッセージが到着すると、システムはイベントまたはコールバックを発生させます
  2. システムをポーリングして、処理するメッセージがあるかどうかを確認する必要があります

オプション1の場合、簡単です。適切なFromEventメソッドでラップするだけで完了です。パブへ!

オプション2の場合、これをどのようにポーリングするか、これを効率的に行う方法を検討する必要があります。また、値を取得した場合、それをどのように公開しますか?

ポーリング専用のスレッドが必要になると思います。他のコーダーがThreadPool / TaskPoolをハンマーで打ってThreadPoolの飢餓状態になることを望まないでしょう。あるいは、コンテキスト切り替えの煩わしさを望まないでしょう(私はそう思います)。したがって、独自のスレッドがあると仮定すると、ポーリングするために座っているWhile / Sleepループのようなものになるでしょう。チェックでいくつかのメッセージが見つかった場合、それらを公開します。これらすべては、Observable.Createに最適に聞こえます。キャンセルを許可するためにDisposableを返すことができないため、今ではおそらくWhileループを使用できません。幸運にも本を全部読んだので、再帰的なスケジューリングに精通しています!

このようなものがうまくいくと思います。#未検証

public class MessageListener
{
    private readonly IObservable<IMessage> _messages;
    private readonly IScheduler _scheduler;

    public MessageListener()
    {
        _scheduler = new EventLoopScheduler();

        var messages = ListenToMessages()
                                    .SubscribeOn(_scheduler)
                                    .Publish();

        _messages = messages;
        messages.Connect();
    }

    public IObservable<IMessage> Messages
    {
        get {return _messages;}
    }

    private IObservable<IMessage> ListenToMessages()
    {
        return Observable.Create<IMessage>(o=>
        {
                return _scheduler.Schedule(recurse=>
                {
                    try
                    {           
                        var messages = GetMessages();
                        foreach (var msg in messages)
                        {
                            o.OnNext(msg);
                        }   
                        recurse();
                    }
                    catch (Exception ex)
                    {
                        o.OnError(ex);
                    }                   
                });
        });
    }

    private IEnumerable<IMessage> GetMessages()
    {
         //Do some work here that gets messages from a queue, 
         // file system, database or other system that cant push 
         // new data at us.
         // 
         //This may return an empty result when no new data is found.
    }
}

私がサブジェクトを本当に好きではない理由は、通常、開発者が問題について明確な設計をしていない場合です。主題をハックし、あちこちでそれを突いて、WTFの貧弱なサポート担当者が推測しているようにしてください。作成/生成などのメソッドを使用する場合、シーケンスへの影響をローカライズします。すべてを1つの方法で確認でき、他の誰も厄介な副作用を投げていないことがわかります。サブジェクトフィールドが表示された場合は、それが使用されているクラスのすべての場所を探す必要があります。一部のMFerが公開している場合、このシーケンスがどのように使用されているかを知っているすべての賭けは無効です!Async / Concurrency / Rxは難しいです。副作用と因果関係のプログラミングで頭をさらに回転させることで、それを難し​​くする必要はありません。


10
私は今この答えを読んでいますが、Subjectインターフェースを公開することは決して考えないことを指摘すべきだと感じました!私はそれを使用して、(IObservable <>を公開する)密封されたクラス内でIObservable <>実装を提供しています。Subject <>インターフェースの公開が悪いことになる理由は間違いなくわかります
Anthony

こんにちは、厚くして申し訳ありませんが、私はあなたのコードを本当に理解していません。ListenToMessages()とGetMessages()は何をして戻ってきますか?
user10479

1
個人的なプロジェクト@jeromergの場合、これで問題ない可能性があります。ただし、私の経験では、開発者がWPF、MVVM、ユニットテストのGUIデザインに苦労してからRxを投入すると、状況がさらに複雑になる可能性があります。BehaviourSubject-as-a-propertyパターンを試しました。ただし、標準のINPCプロパティを使用してから、単純な拡張メソッドを使用してこれをIObservableに変換すると、他のユーザーにとってはるかに採用しやすいことがわかりました。さらに、動作サブジェクトを操作するには、カスタムWPFバインディングが必要です。貧しいチームは、WPF、MVVM、Rx、そして新しいフレームワークについても学ぶ必要があります。
リーキャンベル、

2
@LeeCampbell、コード例で言えば、通常の方法は、MessageListenerがシステムによって構築され(おそらくクラス名を何らかの方法で登録する)、システムはOnCreate()を呼び出し、 OnGoodbye()。メッセージが生成されると、message1()、message2()、およびmessage3()を呼び出します。messageX [123]は件名でOnNextを呼び出すようですが、もっと良い方法はありますか?
James Moore

1
@JamesMoore。これらのことは、具体的な例で説明する方がはるかに簡単です。Rxと件名を使用するオープンソースのAndroidアプリをご存知の場合は、もっと良い方法を提供できるかどうかを確認する時間を見つけることができます。台座の上に立って被験者が悪いと言ってもあまり役に立たないことは理解しています。しかし、IntroToRx、RxCookbook、ReactiveTraderなどはすべて、Rxの使用例のさまざまなレベルの例を提供していると思います。
リーキャンベル

38

一般的にはを使用しないでくださいSubject。ただし、ここで行うことについては、非常にうまく機能すると思います。Rxチュートリアルで「サブジェクトを回避する」というメッセージに出くわしたときに、同様の質問をしました。

(Rxxの)Dave Sextonを引用するには

「サブジェクトはRxのステートフルなコンポーネントです。フィールドやローカル変数としてイベントのようなオブザーバブルを作成する必要がある場合に役立ちます。」

私はそれらをRxへのエントリポイントとして使用する傾向があります。したがって、(何かが起こった)と言う必要があるコードがある場合は、とを使用しSubjectますOnNext。次にIObservable、他のユーザーがサブスクライブするためにそれを公開AsObservable()します(サブジェクトで使用して、だれもサブジェクトにキャストして台無しにできないことを確認できます)。

.NETイベントでこれを達成してを使用することもできますがFromEventPatternIObservableとにかくイベントを変換するだけの場合は、代わりにイベントを使用するメリットがわかりませんSubject(つまり、ここに何か)

ただし、避けるべきことは、IObservablewithをにサブスクライブすることSubject、つまりSubjectIObservable.Subscribeメソッドにa を渡さないことです。


なぜ州が必要なのですか?私の回答で示されているように、問題を個別の部分に分解すると、実際には状態を管理する必要がまったくなくなります。この場合、被験者使用しないください
casperOne 2013年

8
@casperOne Subject <T>またはイベント(どちらにも呼び出すもののコレクション、オブザーバー、またはイベントハンドラーがある)の外側の状態は必要ありません。イベントを追加する唯一の理由がFromEventPatternでラップすることである場合、私はサブジェクトを使用することを好みます。あなたにとって重要かもしれない例外の回路図の変更は別として、私はこのようにサブジェクトを回避することの利点を見ていません。繰り返しますが、ここでは、このイベントがサブジェクトよりも好ましい何かが欠けている可能性があります。州についての言及は引用の一部にすぎず、そのままにしておく方がよいように思われました。おそらく、その部分がなければより明確になるでしょうか?
Wilka、2013年

@casperOne-ただし、FromEventPatternでラップするためだけにイベントを作成しないでください。それは明らかにひどい考えです。
James Moore 14

3
私はこのブログ投稿で私の引用をより詳しく説明しました。
Dave Sexton 2014

私はそれらをRxへのエントリポイントとして使用する傾向があります。これで頭に釘が当たった。呼び出されたときに、リアクティブ処理パイプラインを通過させたいイベントを生成するAPIがある状況があります。FromEventPatternがRxJava AFAICTに存在しないように見えるので、件名は私にとっての答えでした。
蠍座

31

多くの場合、サブジェクトを管理しているときは、実際にはRxにすでに含まれている機能を再実装しているだけであり、おそらく堅牢でシンプルで拡張可能な方法ではありません。

いくつかの非同期データフローをRxに適合させようとしている(または現在非同期ではないものから非同期データフローを作成している)場合、最も一般的なケースは通常次のとおりです。

  • データのソースはイベントです。Leeが言うように、これは最も単純なケースです。FromEventを使用してパブに向かいます。

  • データのソースは同期操作からのものであり、ポーリングされた更新(Webサービスやデータベースの呼び出しなど)が必要です。この場合は、Leeの提案するアプローチを使用できますObservable.Interval.Select(_ => <db fetch>)。単純な場合は、のようなものを使用できます。DistinctUntilChanged()を使用して、ソースデータで何も変更されていない場合に更新を発行しないようにすることができます。

  • データのソースは、コールバックを呼び出す一種の非同期APIです。この場合、Observable.Createを使用してコールバックをフックし、オブザーバーでOnNext / OnError / OnCompleteを呼び出します。

  • データのソースは、新しいデータが利用可能になるまでブロックする呼び出しです(たとえば、いくつかの同期ソケット読み取り操作):この場合、Observable.Createを使用して、ソケットから読み取り、Observer.OnNextにパブリッシュする命令コードをラップできます。データが読み取られたとき。これは、件名で行っていることと似ている場合があります。

Observable.Createを使用するか、Subjectを管理するクラスを作成するかは、yieldキーワードを使用するか、IEnumeratorを実装するクラス全体を作成するかとほぼ同じです。もちろん、IEnumeratorを生成して、yieldコードと同じくらいクリーンで良質な市民になることができますが、どちらがよりカプセル化されていて、デザインがすっきりしていると思いますか?同じことは、Observable.Createと管理対象の場合にも当てはまります。

Observable.Createは、レイジーセットアップとクリーンティアダウンのためのクリーンなパターンを提供します。サブジェクトをラップするクラスでこれをどのように達成しますか?ある種のStartメソッドが必要です...いつそれを呼び出すかはどうやってわかりますか?それとも、誰も聞いていないときでも、いつもそれを始めますか?そして、完了したら、ソケットからの読み取りやデータベースのポーリングなどを停止する方法を教えてください。何らかのStopメソッドが必要であり、サブスクライブしているIObservableだけでなく、そもそもSubjectを作成したクラスにもアクセスできる必要があります。

Observable.Createでは、すべてが1つの場所にまとめられています。Observable.Createの本体は、誰かがサブスクライブするまで実行されないため、サブスクライブされたユーザーがいない場合は、リソースを使用しません。そして、Observable.Createは、リソースやコールバックなどを正常にシャットダウンできるDisposableを返します。これは、Observerがサブスクライブを解除したときに呼び出されます。Observableの生成に使用しているリソースのライフタイムは、Observable自体のライフタイムと適切に関連付けられています。


1
Observable.Createの非常に明確な説明。ありがとうございました!
エヴァンモラン

1
サブジェクトを使用するケースがまだあります。ブローカーオブジェクトはオブザーバブルを公開します(たとえば、変更可能なプロパティだけを言います)。さまざまなコンポーネントがブローカーを呼び出し、そのプロパティが(メソッド呼び出しで)変更されたとき、そのメソッドがOnNextを実行します。消費者は購読します。この場合、BehaviorSubjectを使用すると思いますが、それは適切ですか?
フランク

1
状況によります。優れたRx設計は、システムを非同期/リアクティブアーキテクチャに変換する傾向があります。リアクティブコードの小さなコンポーネントを、必須設計のシステムときれいに統合するのは難しい場合があります。バンドエイドソリューションは、サブジェクトを使用して命令型アクション(関数呼び出し、プロパティセット)を監視可能なイベントに変換することです。その後、リアクティブなコードの小さなポケットになり、本当の「あはは!」瞬間。データフローをモデル化してそれに反応するようにデザインを変更すると、通常はより良いデザインが得られますが、これは広範囲にわたる変更であり、考え方のシフトとチームの賛同が必要です。
Niall Connaughton

1
私はここで(Rxは経験の浅い)と述べます:サブジェクトを使用することで、成長した命令型アプリケーション内でRxの世界に入り、ゆっくりと変換することができます。また、最初の経験を得るために....そして確かに後でコードを最初から本来あるべき状態に変更します(笑)。しかし、そもそもサブジェクトを使用する価値があると思います。
ロベット

9

引用されたブロックテキストは、を使用してはならない理由をかなり説明していますがSubject<T>、簡単にするために、オブザーバーとオブザーバブルの機能を組み合わせ、その間に(カプセル化するか拡張するかにかかわらず)ある種の状態を注入しています。

ここで問題が発生します。これらの責任は、個別に区別する必要があります。

つまり、あなたの特定のいえ、ケースでは、懸念事項を小さな部分に分割することをお勧めします。

まず、スレッドがホットで、常にハードウェアを監視して、通知を発生させるシグナルを探します。通常、これをどのように行いますか? イベント。それではまず始めましょう。

EventArgsイベントを発生させることを定義しましょう。

// The event args that has the information.
public class BaseFrameEventArgs : EventArgs
{
    public BaseFrameEventArgs(IBaseFrame baseFrame)
    {
        // Validate parameters.
        if (baseFrame == null) throw new ArgumentNullException("IBaseFrame");

        // Set values.
        BaseFrame = baseFrame;
    }

    // Poor man's immutability.
    public IBaseFrame BaseFrame { get; private set; }
}

これで、イベントを発生させるクラスです。注、これは(あなたが常にハードウェアバッファを監視実行中のスレッドを持っているので)、静的クラス、またはあなたがオンデマンドに加入して呼んで何かできています。これは必要に応じて変更する必要があります。

public class BaseFrameMonitor
{
    // You want to make this access thread safe
    public event EventHandler<BaseFrameEventArgs> HardwareEvent;

    public BaseFrameMonitor()
    {
        // Create/subscribe to your thread that
        // drains hardware signals.
    }
}

これで、イベントを公開するクラスができました。Observablesはイベントに適しています。そんなにので、イベントのストリームを変換するための最初のクラスのサポートがあることに(イベントの複数回の発射などのイベントストリームを考える)IObservable<T>あなたは、標準のイベントパターンに従っている場合を通じて、実装静的のFromEventPattern方法Observableクラス

イベントのソースとFromEventPatternメソッドを使用して、次のIObservable<EventPattern<BaseFrameEventArgs>>ように簡単に作成できます(EventPattern<TEventArgs>クラスは、.NETイベントに表示されるもの、特に、派生EventArgs元のインスタンスと送信者を表すオブジェクトを具体化します)。

// The event source.
// Or you might not need this if your class is static and exposes
// the event as a static event.
var source = new BaseFrameMonitor();

// Create the observable.  It's going to be hot
// as the events are hot.
IObservable<EventPattern<BaseFrameEventArgs>> observable = Observable.
    FromEventPattern<BaseFrameEventArgs>(
        h => source.HardwareEvent += h,
        h => source.HardwareEvent -= h);

もちろん、あなたがしたいIObservable<IBaseFrame>が、それは簡単です、使用してSelect拡張メソッドをObservable(あなたのようなLINQでする、と私たちは使いやすい方法でこれまでのすべてをラップすることができます)投影を作成するクラス:

public IObservable<IBaseFrame> CreateHardwareObservable()
{
    // The event source.
    // Or you might not need this if your class is static and exposes
    // the event as a static event.
    var source = new BaseFrameMonitor();

    // Create the observable.  It's going to be hot
    // as the events are hot.
    IObservable<EventPattern<BaseFrameEventArgs>> observable = Observable.
        FromEventPattern<BaseFrameEventArgs>(
            h => source.HardwareEvent += h,
            h => source.HardwareEvent -= h);

    // Return the observable, but projected.
    return observable.Select(i => i.EventArgs.BaseFrame);
}

7
@casperOneの応答ありがとうございます。これは私の最初のアプローチでしたが、Rxでラップできるようにイベントを追加するのは「間違っている」と感じました。私は現在、デリゲートを使用しています(そうです、まさにそれがイベントと同じです!)、構成の読み込みと保存に使用されるコードに適合します。これは、コンポーネントパイプラインを再構築できる必要があり、デリゲートシステムが私に最も与えました柔軟性。Rxは私にこの分野で頭痛の種を与えていますが、フレームワークの他のすべての力が構成の問題を解決することを非常に価値のあるものにしています。
アンソニー

@Anthony彼のコードサンプルを機能させることができればすばらしいですが、私がコメントしたように、それは意味がありません。「間違っている」と感じるのは、論理的な部分に細分するのがなぜ「間違っている」ように見えるのかはわかりませんが、元の投稿に、これをどのように変換するのが最も適切かを示す十分な詳細IObservable<T>情報がないため、方法に関する情報がないためです。現在、その情報を使用してシグナリングが行われています。
casperOne 2013年

@casperOneあなたの意見では、Subjectの使用はメッセージバス/イベントアグリゲータに適していますか?
kitsune

1
@kitsuneいいえ、なぜそうなるのかわかりません。「最適化」を考えている場合、それが問題であるかどうかを質問する必要があります。問題の原因であるRxを測定しましたか?
casperOne 2013

2
私はここでcasperOneに同意します。懸念を分割することは良い考えです。Hardware to Event to Rxパターンを使用すると、エラーのセマンティクスが失われることを指摘しておきます。失われた接続やセッションなどは、消費者に公開されません。これで、コンシューマは、再試行するのか、切断するのか、別のシーケンスにサブスクライブするのか、それとも別のものにするのかを決定できません。
リーキャンベル

0

サブジェクトがパブリックインターフェイスでの使用に適していないことを一般化するのは悪いことです。これは確かに本当ですが、これはリアクティブプログラミングアプローチのように見える方法ではないということですが、クラシックコードの改善/リファクタリングのオプションとしては間違いなく優れています。

パブリックセットアクセサーを備えた通常のプロパティがあり、変更について通知したい場合、BehaviorSubjectで置き換えることには何の意味もありません。INPCやその他のイベントはそれほどクリーンではなく、個人的に私を疲れさせます。この目的のために、通常のプロパティの代わりにBehaviorSubjectsをパブリックプロパティとして使用し、INPCまたは他のイベントを破棄する必要があります。

さらに、サブジェクトインターフェイスを使用すると、インターフェイスのユーザーはプロパティの機能についてより認識しやすくなり、値を取得するだけでなくサブスクライブする可能性が高くなります。

プロパティの変更を他の人に聞いて/サブスクライブさせたい場合に使用するのが最適です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.