15

私はHeadFirstDesign Patternsの本を読み、コードをJavaからC#に変換するために最善を尽くしています。本がオブザーバーパターンについて議論した後、.NET4と同様に、Javaにはクラス/インターフェースが組み込まれていると述べました。それで、私はそれを適切に使用する方法を研究し始めました、そして私はそれの方法を除いてそれのほとんどを理解しましたSubscribe()

IObserverをサブスクライブしようとしたときにMSDNの記事を見ると、メソッドはIDisposableを返します。なぜそれが必要なのでしょうか?メソッド引数に基づいてIObserverのサブスクライブを解除するメソッドを実装しないのはなぜですか?IDisposableインターフェースを使用する理由を調査しました。私もこれを読みましたが、違い/それが私に伝えようとしていたことを完全には理解していませんでした:

IDisposableインターフェースへの参照を返します。これにより、オブザーバーは、プロバイダーが通知の送信を終了してサブスクライバーのOnCompletedメソッドを呼び出す前に、サブスクライブを解除(つまり、通知の受信を停止)できます。

4

6 に答える 6

8

サブスクリプションをキャンセルするために必要な情報は、イベント発行者がサブスクリプションを管理する方法によって異なります。イベントに使用されるアプローチ(Removeデリゲートが以前にメソッドに渡したメソッドに渡すAdd)は、ある程度実行可能ですが、いくつかの重大な欠陥があります。その中で:

  1. 多くの場合、イベント発行者は、サブスクリプションに関連する情報を含むレコードを見つけるために線形検索を実行する必要があります。サブスクライバーが多いイベントの可能性がある場合、これによりO(N ^ 2)の動作が不必要に発生する可能性があります。サブスクライバーが何らかのリンクリスト(リンクされたオブジェクトまたはインデックスにリンクされた配列スロット)に保持され、サブスクリプション解除要求がキャンセルされるサブスクリプションに関する情報を保持している場合、サブスクリプションとサブスクリプション解除の両方を一定時間で処理できます。さらに、サブスクリプション解除は、「Finalize」コンテキストで安全に実行できるロックフリーの非ブロッキング方式で(競合していないアレイスロットである可能性が最も高い1つの「CompareExchange」を使用して)安全かつ簡単に処理できます。
  2. 1人のデリゲートが処理の順序が重要なイベントに複数回サブスクライブされ、コードが最初のサブスクリプションをキャンセルしようとすると、最後のサブスクリプションはキャンセルされ、最初のサブスクリプションは有効なままになります。
  3. デリゲート`D`がサブスクライブされ、` A`、 `B`、` C`、および `D`を含むマルチキャストデリゲート`ABCD`がサブスクライブされ、その後 `D`がサブスクライブ解除されると、デリゲート`DABC`は残ります。コードが`ABCD`のサブスクライブを解除しようとしても、順番にサブスクライブします。`delegateType.Combine`の代わりに`List`を使用すればこの問題を回避できますが、他の問題は残ることに注意してください。

イベントサブスクリプションメソッドにサブスクリプションのキャンセルに使用できるオブジェクトを返すようにすると、これらの問題を回避できます。次に、最大の問題は、どのタイプのオブジェクトを使用する必要があるかということです。3つの選択肢が思い浮かびます。

  1. デリゲート(おそらくパラメータなし、 `void`を返す)
  2. サブスクリプションをキャンセルするための単一のメソッド(おそらくパラメーターなし、 `void`を返す)を備えた一部の`ICancelSubscription`インターフェース
  3. 存在するインターフェースである`IDisposable`は、単一のパラメーターなしのメソッドを持ち、クリーンアップ関連の目的で広く使用されています。

デリゲートを使用するのが妥当な選択です。ユーザーがその情報がどのような形式になるかを心配することなく、サブスクリプションをキャンセルするために必要な情報を簡単にカプセル化できます。デリゲートを使用すると、少なくとも1つの追加のヒープオブジェクト(デリゲート自体用)と、場合によっては2つ(2番目はサブスクリプション解除情報を保持するオブジェクト)の割り当てが必要になります。使用することは、 ;ではなくIDisposable呼び出すことを除いて、デリゲートを使用することと本質的に同じです。ただし、多くの場合、効率の点でわずかな利点があります。他のインターフェースを使用することも可能ですが、既存のインターフェースを使用するよりも実際には何の利点もありません。DisposeInvokeIDisposableIDisposable

于 2012-05-16T20:38:27.513 に答える
4

ここでのあなたの主な質問は、Microsoftがなぜ選択するのかということのようです。

interface IObservable<T>
{
    IDisposable Subscribe(IObserver<T> observer);
}

それ以外の

interface IObservable<T>
{
    void Subscribe(IObserver<T> observer);
    void Unsubscribe(IObserver<T> observer);
}

私はデザインミーティングやそこから生まれた推論に精通していませんが、この選択が行われた理由はいくつか推測できます。

最初の形式を選択するために私が考えることができる最も良い理由は、observableとobserverの両方ではなく、Subscribeから返されたIDisposableのみを追跡できることです。たとえば、次のようなコードがあるとします。

var obsSource = /* some observable source */
var subscription = obsSource.Where(x => x != null).Subscribe(Console.WriteLine);
// stuff...
subscription.Dispose();

この状況では、サブスクライブされたobservable(から返される)への参照を保持したり、 ( extensionメソッドWhereを介して)オブザーバーを明示的に作成したりする必要はありません。Subscribe2番目のオプションが選択された場合は、代わりにこれを行う必要があります。

var obsSource = /* some observable source */
var filtered = obsSource.Where(x => x != null);
var observer = Observer.Create(Console.WriteLine);
filtered.Subscribe(observer);
// stuff...
filtered.Unsubscribe(observer);

表面的には、これはそれほど違いはありませんが、別の質問で説明したように、サブスクリプションが作成された後は、中間のオブザーバブルを保持する必要はありません。2番目のアプローチを採用した場合でも、チェーンの各ステップのオブザーバブルを作成して保持する必要があります。また、中間のオブザーバブルへの参照も保持する必要があります。

バージョン1は、IEnumerable方法2よりも見やすい二重性を実現します。これは初期設計の一部であった可能性がありますが、長距離の理由としてはそれほど重要ではありません。

于 2012-05-16T23:11:43.557 に答える
4

サブスクライブメソッドとアンサブスクライブメソッドのペアは、構成的ではありません。すべてのオペレーターは、Subscribeに渡されたオブザーバーのディクショナリを保持し、依存するオブザーバブルシーケンスに渡された(オペレーターに渡された)各オブザーバーインスタンスにそれらをマッピングする必要があります。

たとえば、2つのソースに対してMerge演算子を作成することを検討してください。今日、これは次のようになります(textareaコンパイル済み):

static IObservable<T> Merge<T>(IObservable<T> xs, IObservable<T> ys)
{
    return Observable.Create<T>(observer =>
    {
        var n = 2;

        var mergeObserver = Observer.Create<T>(
            observer.OnNext,
            observer.OnError,
            () =>
            {
                // protected by the gate, see use of Synchronize below
                if (--n == 0)
                    observer.OnCompleted();
            }
        );

        var gate = new object();

        return new CompositeDisposable(
            xs.Synchronize(gate).Subscribe(mergeObserver),
            ys.Synchronize(gate).Subscribe(mergeObserver)
        );
    });
}

ご覧のとおり、シーケンスの構成は、Subscribe呼び出しから返されるIDisposableオブジェクトの構成にもつながります。Observable.Createでは、指定されたオブザーバーに端末メッセージを送信すると、返されたIDisposableを自動的に破棄する多くのことが行われていることに注意してください。この場合、observer.OnErrorとobserver.OnCompletedを呼び出すと、CompositeDisposableで両方のサブスクリプションが破棄されます。(しかし、それはしばらくの間話すのとはまったく異なる主題です。)

以下のコードは、IObservableにSubscribe / Unsubscribeペアが存在することを前提としています(したがって、2つのアクションを持つCreateファクトリメソッドを使用)。

static IObservable<T> Merge<T>(IObservable<T> xs, IObservable<T> ys)
{
    var map = new Dictionary<IObserver<T>, IObserver<T>>();

    return Observable.Create<T>(
        subscribe: observer =>
        {
            var gate = new object();
            var n = 2;

            var mergeObserver = Observer.Create<T>(
                x =>
                {
                    lock (gate)
                        observer.OnNext(x);
                },
                ex =>
                {
                    lock (gate)
                        observer.OnError(ex);
                },
                () =>
                {
                    lock (gate)
                        if (--n == 0)
                            observer.OnCompleted();
                }
            );

            //
            // Using .Synchronize(gate) would be a mess, because then we need to
            // keep the  two synchronized sequences around as well, such that we
            // can call Unsubscribe on those. So, we're "better off" inlining the
            // locking code in the observer.
            //
            // (Or: how composition goes down the drain!)
            //
            xs.Subscribe(mergeObserver);
            ys.Subscribe(mergeObserver);

            lock (map)
                map[observer] = mergeObserver;
        },
        unsubscribe: observer =>
        {
            var mergeObserver = default(IObserver<T>);
            lock (map)
                map.TryGetValue(observer, out mergeObserver);

            if (mergeObserver != null)
            {
                xs.Unsubscribe(mergeObserver);
                ys.Unsubscribe(mergeObserver);
            }
        }
    );
}

これは架空のものであることに注意してください。これ以上のエッジケースについても、OnErrorまたはOnCompletedの呼び出し時にそれ自体をクリーンアップするためにこのCreateがどのように機能するかについても考えていません。また、例としてマージを使用すると、「購読解除」中に気にする他のリソース(スケジューラージョブなど)がないのは幸運です。

お役に立てれば、

-バート(Rxチーム)

于 2012-05-23T22:55:37.913 に答える
1

unsubscribeメソッドを呼び出すということは、サブスクライブしたのと同じインスタンスを渡す必要があることを意味します。参照を保持する必要があります。さまざまなタイプのオブザーバブルが多数ある場合は、さまざまな参照を保持する必要があります。

unsubscribeメソッドを使用すると、コードのクリーンアップが煩雑になります。

ただし、IDisposableすべての参照を保持するために必要な構造は1つだけです- List<IDisposable>(またはRxを使用できますCompositeDisposable)。

これで、クリーンアップコードを非常にきちんと整理することができます。

さらに進んでIDisposable、Rxサブスクリプションだけでなく、すべてのクリーンアップコードのインスタンスを作成しました。それは人生をとても楽にします。

于 2012-05-17T00:30:17.730 に答える
1

与えられた他の理由に加えて、あなたもブロックIDisposableを使うことができます。using

using (var subscription = source.Subscribe(observer))
{
    // ...
}
于 2012-05-17T13:20:07.970 に答える
0

これにより、オブザーバー自体が破棄されたときにオブザーバーのサブスクリプションが終了することを非常に簡単に確認できます。通常、SubscribeはIObservableを実装するクラスによって呼び出されることを考慮してください。

class MyObserver<Foo> : IObserver<Foo>, IDisposable {

    private IDisposable _subscription;

    public MyObserver(IObservable<T> eventSource) {
        _subscription = eventSource.Subscribe(this);
    }

    // implementation of IObservable members here

    public void Dispose() {
        _subscription.Dispose();
    }
}

これで、MyObserverインスタンスが破棄されると、サブスクリプションも一緒に自動的に破棄されます。

public partial class MyGUIThing : Form {
    private MyObservable<Foo> _observer = new MyObservable<Foo>(someEventSource);
    // whatever else
}

このフォームのインスタンスが作成されると、サブスクリプションが開始されます。フォームが閉じられて_observer確定すると、サブスクリプションも自動的に破棄されます。

于 2012-05-16T20:38:54.307 に答える