从头开始实现IObservable <t> </t>

时间:2009-11-20 07:57:44

标签: c# system.reactive

Reactive Extensions带有许多辅助方法,用于将现有事件和异步操作转换为可观察对象,但是如何实现IObservable&lt; T&gt;从头开始?

IEnumerable有一个可爱的yield关键字,使其实现起来非常简单。

实施IObservable&lt; T&gt;的正确方法是什么?

我是否需要担心线程安全?

我知道支持在特定的同步上下文中回调,但这是我作为IObservable&lt; T&gt;作者需要担心还是以某种方式内置?

更新

这是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

5 个答案:

答案 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东西......

我自己的经历表明:

  • 订阅observables的人不应该从订阅中抛出。当用户抛出时,观察者无法做任何合理的事情。 (这类似于事件。)很可能异常只会冒泡到顶级catch-all处理程序或者崩溃应用程序。
  • 来源可能应该是“逻辑单线程”。我认为编写可以对并发OnNext调用作出反应的客户端可能更难;即使每个单独的调用来自不同的线程,避免并发调用也是有帮助的。
  • 拥有一个强制执行某些“合同”的基础/助手类非常有用。

如果人们可以在这些方面展示更具体的建议,我很好奇。

答案 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)

  1. 破解反射器并看看。

  2. 观看一些C9视频 - this了解如何“推导”选择'组合'

  3. 秘诀是创建AnonymousObservable,AnonymousObserver和AnonymousDisposable类(这些只是因为你无法实例化接口而解决)。当你使用Actions和Funcs传递它时,它们包含零实现。

  4. 例如:

    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