web-dev-qa-db-ja.com

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

現在、.NETのReactive Extensionsフレームワークに慣れており、見つけたさまざまな紹介リソース(主に http://www.introtorx.com )を進めています。

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

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

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

件名タイプの使用は避けてください。 Rxは事実上、関数型プログラミングのパラダイムです。サブジェクトを使用することは、現在状態を管理していることを意味し、潜在的に変化しています。変化する状態と非同期プログラミングの両方を同時に処理することは非常に困難です。さらに、多くのオペレーター(拡張メソッド)は、サブスクリプションとシーケンスの正確で一貫した存続期間が維持されるように慎重に作成されています。主題を導入するとき、これを破ることができます。サブジェクトを明示的に使用すると、将来のリリースでパフォーマンスが大幅に低下する可能性があります。

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

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

どんなアドバイスも大歓迎です。

99
Anthony

私の独断的な方法を無視し、「主題が良い/悪い」をすべて無視する場合、[OK]を。問題空間を見てみましょう。

私はあなたがどちらにあなたがingrateする必要があるシステムの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が進行中であることを貧しいサポート開発者に推測させます。 Create/Generate etcメソッドを使用すると、シーケンスの効果をローカライズします。すべてを1つの方法で見ることができ、他の誰も厄介な副作用を引き起こしていないことを知っています。件名フィールドが表示された場合、使用されているクラス内のすべての場所を検索する必要があります。 MFerが公開している場合、すべての賭けはオフになり、このシーケンスがどのように使用されているかを知っています。 Async/Concurrency/Rxは難しいです。副作用や因果関係のプログラミングがあなたの頭をさらに回すことを可能にすることによって、それを難し​​くする必要はありません。

62
Lee Campbell

一般に、Subjectの使用は避けてください。ただし、ここで行っていることについては、非常にうまく機能すると思います。 Rxチュートリアルで「被験者を避ける」というメッセージに出くわしたときに、 同様の質問 を尋ねました。

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

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

Rxへのエントリポイントとして使用する傾向があります。したがって、「何かが起こった」と言う必要があるコードがある場合(あなたが持っているように)、Subjectを使用してOnNextを呼び出します。次に、それをIObservableとして公開して、他の人がサブスクライブできるようにします(サブジェクトでAsObservable()を使用して、誰もサブジェクトにキャストできず、混乱させることができます)。

.NETイベントでこれを実現し、FromEventPatternを使用することもできますが、とにかくイベントをIObservableに変換するだけの場合は、 Subjectではなくイベント

ただし、IObservableを使用してSubjectをサブスクライブすることは非常に強く避ける必要があります。つまり、IObservable.SubscribeメソッドにSubjectを渡さないでください。

31
Wilka

多くの場合、サブジェクトを管理しているときは、実際には既に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とSubjectの管理についても同様です。

Observable.Createは、遅延セットアップとクリーンティアダウンのためのクリーンパターンを提供します。 Subjectをラップするクラスでこれをどのように実現しますか?何らかの種類のStartメソッドが必要です...いつ呼び出すべきかをどのように知るのですかまたは、誰も聞いていない場合でも、常に開始しますか?そして、完了したら、どのようにしてソケットからの読み取り/データベースのポーリングなどを停止するのですか?何らかの種類のStopメソッドが必要であり、サブスクライブしているIObservableだけでなく、そもそもSubjectを作成したクラスにもアクセスできる必要があります。

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

23

引用されたブロックテキストは、 Subject<T> ですが、簡単にするために、observerとobservableの機能を組み合わせて、間に何らかの状態を挿入しています(カプセル化しているのか、拡張しているのか)。

ここで問題が発生します。これらの責任は、互いに分離し、明確にする必要があります。

とはいえ、specificの場合、懸念をより小さな部分に分割することをお勧めします。

まず、ホットなスレッドを使用し、常に通知を生成する信号がないかハードウェアを監視します。どうやってこれを普通にやりますか? イベント 。それでは、それから始めましょう。

イベントが発生する 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; }
}

次に、イベントを発生させるクラス。これは、静的クラス(常にハードウェアバッファを監視するスレッドを実行しているため)、またはthatにサブスクライブするオンデマンドで呼び出すものである可能性があります。必要に応じてこれを変更する必要があります。

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.
    }
}

これで、イベントを公開するクラスができました。オブザーバブルはイベントに適しています。イベントのストリーム(イベントストリームをイベントの複数の発火と考える)を IObservable<T>FromEventPattern classstatic Observable method を介して標準イベントパターンに従う場合の実装。

イベントのソースとFromEventPatternメソッドを使用して、IObservable<EventPattern<BaseFrameEventArgs>>簡単に( EventPattern<TEventArgs> class .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);
}
8
casperOne

サブジェクトがパブリックインターフェイスに使用するのが適切でないことを一般化するのは悪いことです。これは確かに真実ですが、これはリアクティブプログラミングアプローチのようには見えませんが、それは間違いなくあなたの古典的なコードの良い改善/リファクタリングオプションです。

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

さらに、サブジェクトインターフェイスは、インターフェイスのユーザーにプロパティの機能をより認識させ、値を取得する代わりにサブスクライブする可能性が高くなります。

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

0
Felix Keil