Reactive Extensions带有许多辅助方法,用于将现有事件和异步操作转换为可观察对象,但是如何实现IObservable< T>从头开始?
IEnumerable有一个可爱的yield关键字,使其实现起来非常简单。
实施IObservable< T>的正确方法是什么?
我是否需要担心线程安全?
我知道支持在特定的同步上下文中回调,但这是我作为IObservable< T>作者需要担心还是以某种方式内置?
更新
这是Brian的F#解决方案的C#版本
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,请不要抛出ObjectDisposedException
答案 0 :(得分:11)
official documentation弃用了自己实现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);
}
我不知道为什么他们没有公开他们的实施,但是嘿,无论如何。
答案 1 :(得分:10)
老实说,我不确定这一切是多么“正确”,但如果根据我迄今为止的经验感觉相当不错。它是F#代码,但希望你能感受到它的味道。它允许你“新建”一个源对象,然后你可以调用Next / Completed / Error on,它管理订阅并在源或客户端做坏事时尝试Assert。
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东西......
我自己的经历表明:
如果人们可以在这些方面展示更具体的建议,我很好奇。
答案 2 :(得分:7)
是的,yield关键字很可爱;也许IObservable(OfT)会有类似的东西? [编辑:在Eric Meijer的PDC '09 talk中,他说“是的,请注意这个空间”,以产生可观察的声明产量。]
对于接近(而不是自己滚动)的内容,请查看“the bottom”wiki的(not yet) 101 Rx Samples,其中团队建议使用Subject(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);
}
答案 3 :(得分:2)
破解反射器并看看。
观看一些C9视频 - this了解如何“推导”选择'组合'
秘诀是创建AnonymousObservable,AnonymousObserver和AnonymousDisposable类(这些只是因为你无法实例化接口而解决)。当你使用Actions和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);
}
}
我会让你解决剩下的问题......这是一个非常好的理解练习。
有一个很好的小线程正在增加here相关问题。
答案 4 :(得分:2)
关于此实施的一句话:
在.net fw 4中引入并发集合之后,最好使用ConcurrentDictioary而不是简单的字典。
它保存了集合上的处理锁。
ADI