简体   繁体   English

从头开始实现IObservable <T>

[英]Implementing IObservable<T> from scratch

The Reactive Extensions come with a lot of helper methods for turning existing events and asynchronous operations into observables but how would you implement an IObservable<T> from scratch? Reactive Extensions带有许多辅助方法,用于将现有事件和异步操作转换为可观察对象,但是如何从头开始实现IObservable <T>?

IEnumerable has the lovely yield keyword to make it very simple to implement. IEnumerable有一个可爱的yield关键字,使其实现起来非常简单。

What is the proper way of implementing IObservable<T>? 实现IObservable <T>的正确方法是什么?

Do I need to worry about thread safety? 我需要担心线程安全吗?

I know there is support for getting called back on a specific synchronization context but is this something I as an IObservable<T> author need to worry about or this somehow built-in? 我知道有人支持在特定的同步上下文中回调,但这是否是我作为IObservable <T>作者需要担心或者以某种方式内置的内容?

update: 更新:

Here's my C# version of Brian's F# solution 这是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();
        }
    }
}

edit: Don't throw ObjectDisposedException if Dispose is called twice 编辑:如果调用Dispose两次,不要抛出ObjectDisposedException

The official documentation deprecates users implementing IObservable themselves. 官方文档不赞成自己实现IObservable的用户。 Instead, users are expected to use the factory method Observable.Create 相反,用户应该使用工厂方法Observable.Create

When possible, implement new operators by composing existing operators. 如果可能,通过组合现有运算符来实现新运算符。 Otherwise implement custom operators using Observable.Create 否则使用Observable.Create实现自定义运算符

It happens that Observable.Create is a trivial wrapper around Reactive's internal class AnonymousObservable : 碰巧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);
}

I don't know why they didn't make their implementation public, but hey, whatever. 我不知道他们为什么不公开他们的实施,但是,嘿,无论如何。

Honestly, I am not sure how 'right' all this is, but if feels pretty good based on my experience so far. 老实说,我不确定这一切是多么'正确',但如果根据我迄今为止的经验感觉相当不错。 It's F# code, but hopefully you get a sense of the flavor. 它是F#代码,但希望你能感受到它的味道。 It lets you 'new up' a source object, which you can then call Next/Completed/Error on, and it manages subscriptions and tries to Assert when the source or clients do bad things. 它允许您“新建”一个源对象,然后可以调用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

I'll be interested in any thoughts about what's good or bad here; 我会对任何有关这里好坏的想法感兴趣; I haven't had a chance to look at all the new Rx stuff from devlabs yet... 我还没有机会从devlabs看到所有新的Rx东西......

My own experiences suggest that: 我自己的经历表明:

  • Those who subscribe to observables should never throw from the subscriptions. 订阅observables的人不应该从订阅中抛出。 There is nothing reasonable an observable can do when a subscriber throws. 当用户抛出时,观察者无法做任何合理的事情。 (This is similar to events.) Most likely the exception will just bubble up to a top-level catch-all handler or crash the app. (这与事件类似。)很可能异常只会冒泡到顶级catch-all处理程序或崩溃应用程序。
  • Sources probably should be "logically single threaded". 来源可能应该是“逻辑单线程”。 I think it may be harder to write clients that can react to concurrent OnNext calls; 我认为编写可以对并发OnNext调用作出反应的客户端可能更难; even if each individual call comes from a different thread, it is helpful to avoid concurrent calls. 即使每个单独的调用来自不同的线程,避免并发调用也是有帮助的。
  • It's definitely useful to have a base/helper class that enforces some 'contracts'. 拥有一个强制执行某些“契约”的基础/助手类非常有用。

I'm very curious if people can show more concrete advice along these lines. 如果人们能够沿着这些方向展示更具体的建议,我会很好奇。

Yes, the yield keyword is lovely; 是的,yield关键字很可爱; maybe there will be something similar for IObservable(OfT)? 也许IObservable(OfT)会有类似的东西? [edit: In Eric Meijer's PDC '09 talk he says "yes, watch this space" to a declarative yield for generating observables.] [编辑:在Eric Meijer的PDC '09谈话中,他说“是的,看这个空间”,以产生可观察的声明产量。

For something close (instead of rolling your own), check out the bottom of the " (not yet) 101 Rx Samples " wiki, where the team suggests use of the Subject(T) class as a "backend" to implement an IObservable(OfT). 对于接近的东西(而不是自己动手),请查看“ (尚未)101 Rx Samples ”维基的底部 ,团队建议使用Subject(T)类作为“后端”来实现IObservable( OFT)。 Here's their example: 这是他们的例子:

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);
}
  1. Crack open Reflector and have a look. 裂开反射器并看看。

  2. Watch some C9 videos - this one shows how you can 'derive' the Select 'combinator' 观看一些C9视频 - 这个视频展示了如何'衍生'选择'组合'

  3. The secret is to create AnonymousObservable, AnonymousObserver and AnonymousDisposable classes, (which are just work arounds for the fact that you can't instantiate interfaces). 秘诀是创建AnonymousObservable,AnonymousObserver和AnonymousDisposable类(这些只是因为你无法实例化接口而解决)。 They contain zero implementation, as you pass that in with Actions and Funcs. 当您使用Actions和Funcs传递它时,它们包含零实现。

For example: 例如:

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);
    }
}

I'll let you work out the rest... it's a very good exercise in understanding. 我会让你解决剩下的问题......这是一个非常好的理解练习。

There's a nice little thread growing here with related questions. 这里有一个很好的小线程有相关的问题。

just one remark regarding this implementation : 关于这个实现只有一个评论:

after concurrent collections being introduced in .net fw 4 it is probably better to use ConcurrentDictioary instead of a simple dictionary. 在.net fw 4中引入并发集合之后,最好使用ConcurrentDictioary而不是简单的字典。

it saves handling locks on the collection. 它节省了集合上的处理锁。

adi. ADI公司。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM