web-dev-qa-db-ja.com

IObservable <T>を最初から実装する

Reactive Extensionsには、既存のイベントと非同期操作をオブザーバブルに変換するための多くのヘルパーメソッドが付属していますが、IObservable <T>を最初からどのように実装しますか?

IEnumerableには、実装を非常に簡単にするための素敵なyieldキーワードがあります。

IObservable <T>を実装する適切な方法は何ですか?

スレッドセーフについて心配する必要がありますか?

特定の同期コンテキストでコールバックされるためのサポートがあることは知っていますが、これはIObservable <T>作成者として私が心配する必要があるものですか、それとも何らかの形で組み込まれていますか?

更新:

これが私のC#バージョンのBrianのF#ソリューションです

using System;
using System.Linq;
using Microsoft.FSharp.Collections;

namespace Jesperll
{
    class Observable<T> : IObservable<T>, IDisposable where T : EventArgs
    {
        private FSharpMap<int, IObserver<T>> subscribers = 
                 FSharpMap<int, IObserver<T>>.Empty;
        private readonly object thisLock = new object();
        private int key;
        private bool isDisposed;

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing && !isDisposed)
            {
                OnCompleted();
                isDisposed = true;
            }
        }

        protected void OnNext(T value)
        {
            if (isDisposed)
            {
                throw new ObjectDisposedException("Observable<T>");
            }

            foreach (IObserver<T> observer in subscribers.Select(kv => kv.Value))
            {
                observer.OnNext(value);
            }
        }

        protected void OnError(Exception exception)
        {
            if (isDisposed)
            {
                throw new ObjectDisposedException("Observable<T>");
            }

            if (exception == null)
            {
                throw new ArgumentNullException("exception");
            }

            foreach (IObserver<T> observer in subscribers.Select(kv => kv.Value))
            {
                observer.OnError(exception);
            }
        }

        protected void OnCompleted()
        {
            if (isDisposed)
            {
                throw new ObjectDisposedException("Observable<T>");
            }

            foreach (IObserver<T> observer in subscribers.Select(kv => kv.Value))
            {
                observer.OnCompleted();
            }
        }

        public IDisposable Subscribe(IObserver<T> observer)
        {
            if (observer == null)
            {
                throw new ArgumentNullException("observer");
            }

            lock (thisLock)
            {
                int k = key++;
                subscribers = subscribers.Add(k, observer);
                return new AnonymousDisposable(() =>
                {
                    lock (thisLock)
                    {
                        subscribers = subscribers.Remove(k);
                    }
                });
            }
        }
    }

    class AnonymousDisposable : IDisposable
    {
        Action dispose;
        public AnonymousDisposable(Action dispose)
        {
            this.dispose = dispose;
        }

        public void Dispose()
        {
            dispose();
        }
    }
}

編集: Disposeが2回呼び出された場合、ObjectDisposedExceptionをスローしないでください

38

正直なところ、これがどれほど「正しい」かはわかりませんが、これまでの私の経験に基づいてかなり気分が良いかどうかはわかりません。これはF#コードですが、味わいを感じていただければ幸いです。これにより、ソースオブジェクトを「新規作成」できます。これにより、Next/Completed/Errorを呼び出すことができ、サブスクリプションを管理し、ソースまたはクライアントが悪いことをしたときにアサートを試みます。

type ObservableSource<'T>() =     // '
    let protect f =
        let mutable ok = false
        try 
            f()
            ok <- true
        finally
            Debug.Assert(ok, "IObserver methods must not throw!")
            // TODO crash?
    let mutable key = 0
    // Why a Map and not a Dictionary?  Someone's OnNext() may unsubscribe, so we need threadsafe 'snapshots' of subscribers to Seq.iter over
    let mutable subscriptions = Map.empty : Map<int,IObserver<'T>>  // '
    let next(x) = subscriptions |> Seq.iter (fun (KeyValue(_,v)) -> protect (fun () -> v.OnNext(x)))
    let completed() = subscriptions |> Seq.iter (fun (KeyValue(_,v)) -> protect (fun () -> v.OnCompleted()))
    let error(e) = subscriptions |> Seq.iter (fun (KeyValue(_,v)) -> protect (fun () -> v.OnError(e)))
    let thisLock = new obj()
    let obs = 
        { new IObservable<'T> with       // '
            member this.Subscribe(o) =
                let k =
                    lock thisLock (fun () ->
                        let k = key
                        key <- key + 1
                        subscriptions <- subscriptions.Add(k, o)
                        k)
                { new IDisposable with 
                    member this.Dispose() = 
                        lock thisLock (fun () -> 
                            subscriptions <- subscriptions.Remove(k)) } }
    let mutable finished = false
    // The methods below are not thread-safe; the source ought not call these methods concurrently
    member this.Next(x) =
        Debug.Assert(not finished, "IObserver is already finished")
        next x
    member this.Completed() =
        Debug.Assert(not finished, "IObserver is already finished")
        finished <- true
        completed()
    member this.Error(e) =
        Debug.Assert(not finished, "IObserver is already finished")
        finished <- true
        error e
    // The object returned here is threadsafe; you can subscribe and unsubscribe (Dispose) concurrently from multiple threads
    member this.Value = obs

ここで何が良いか悪いかについての考えに興味があります。私はまだdevlabsからのすべての新しいRxのものを見る機会がありませんでした...

私自身の経験はそれを示唆しています:

  • オブザーバブルをサブスクライブする人は、サブスクリプションから決してスローしないでください。サブスクライバーがスローしたときに、オブザーバブルが実行できる合理的なことは何もありません。 (これはイベントに似ています。)ほとんどの場合、例外はトップレベルのキャッチオールハンドラーにバブルアップするか、アプリをクラッシュさせます。
  • ソースはおそらく「論理的にシングルスレッド」である必要があります。同時OnNext呼び出しに反応できるクライアントを作成するのは難しいかもしれません。個々の呼び出しが異なるスレッドからのものであっても、同時呼び出しを避けることは役に立ちます。
  • いくつかの「コントラクト」を強制するベース/ヘルパークラスがあると間違いなく便利です。

人々がこれらの線に沿ってより具体的なアドバイスを示すことができるかどうか私は非常に興味があります。

10
Brian

公式ドキュメント IObservableを実装しているユーザーを非推奨にします。代わりに、ユーザーはファクトリメソッドを使用することが期待されています Observable.Create

可能であれば、既存の演算子を作成して新しい演算子を実装します。それ以外の場合は、Observable.Createを使用してカスタム演算子を実装します

Observable.Createは、Reactiveの内部クラスAnonymousObservableの簡単なラッパーであることがあります。

public static IObservable<TSource> Create<TSource>(Func<IObserver<TSource>, IDisposable> subscribe)
{
    if (subscribe == null)
    {
        throw new ArgumentNullException("subscribe");
    }
    return new AnonymousObservable<TSource>(subscribe);
}

なぜ彼らが実装を公開しなかったのかはわかりませんが、ねえ、何でも。

11
Colonel Panic

はい、yieldキーワードは素敵です。 IObservable(OfT)にも似たようなものがあるのでしょうか? [編集:Eric Meijerの PDC '09トーク 彼は「はい、このスペースを見てください」と言って、観測量を生成するための宣言型の利回りを示しています。]

(自分でロールするのではなく)近いものについては、 " (まだ)101 Rxサンプル "ウィキの をチェックしてください。ここでは、チームがサブジェクトの使用を提案しています。 (T)IObservable(OfT)を実装するための「バックエンド」としてのクラス。これが彼らの例です:

public class Order
{            
    private DateTime? _paidDate;

    private readonly Subject<Order> _paidSubj = new Subject<Order>();
    public IObservable<Order> Paid { get { return _paidSubj.AsObservable(); } }

    public void MarkPaid(DateTime paidDate)
    {
        _paidDate = paidDate;                
        _paidSubj.OnNext(this); // Raise PAID event
    }
}

private static void Main()
{
    var order = new Order();
    order.Paid.Subscribe(_ => Console.WriteLine("Paid")); // Subscribe

    order.MarkPaid(DateTime.Now);
}
7
David Cuccia
  1. リフレクターを割って開いて見てください。

  2. いくつかのC9ビデオを見る- this 1つは、「選択」コンビネータを「導出」する方法を示しています。

  3. 秘訣は、AnonymousObservable、AnonymousObserver、およびAnonymousDisposableクラスを作成することです(これらは、インターフェイスをインスタンス化できないという事実の回避策にすぎません)。 Actions and Funcsでそれを渡すと、実装はゼロになります。

例えば:

public class AnonymousObservable<T> : IObservable<T>
{
    private Func<IObserver<T>, IDisposable> _subscribe;
    public AnonymousObservable(Func<IObserver<T>, IDisposable> subscribe)
    {
        _subscribe = subscribe;
    }

    public IDisposable Subscribe(IObserver<T> observer)
    {
        return _subscribe(observer);
    }
}

私はあなたに残りを解決させます...それは理解するのに非常に良い練習です。

素敵な小さなスレッドが成長しています ここ 関連する質問があります。

2
Benjol

この実装に関する1つのコメント:

.net fw 4で並行コレクションが導入された後は、単純な辞書の代わりにConcurrentDictioaryを使用する方がおそらく良いでしょう。

コレクションのロックの処理を節約します。

adi。

2
Adiel Yaacov