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をスローしないでください
正直なところ、これがどれほど「正しい」かはわかりませんが、これまでの私の経験に基づいてかなり気分が良いかどうかはわかりません。これは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のものを見る機会がありませんでした...
私自身の経験はそれを示唆しています:
人々がこれらの線に沿ってより具体的なアドバイスを示すことができるかどうか私は非常に興味があります。
公式ドキュメント 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);
}
なぜ彼らが実装を公開しなかったのかはわかりませんが、ねえ、何でも。
はい、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);
}
リフレクターを割って開いて見てください。
いくつかのC9ビデオを見る- this 1つは、「選択」コンビネータを「導出」する方法を示しています。
秘訣は、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);
}
}
私はあなたに残りを解決させます...それは理解するのに非常に良い練習です。
素敵な小さなスレッドが成長しています ここ 関連する質問があります。
この実装に関する1つのコメント:
.net fw 4で並行コレクションが導入された後は、単純な辞書の代わりにConcurrentDictioaryを使用する方がおそらく良いでしょう。
コレクションのロックの処理を節約します。
adi。