簡體   English   中英

從頭開始實現IObservable <T>

[英]Implementing IObservable<T> from scratch

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

官方文檔不贊成自己實現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);
}

我不知道他們為什么不公開他們的實施,但是,嘿,無論如何。

老實說,我不確定這一切是多么'正確',但如果根據我迄今為止的經驗感覺相當不錯。 它是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調用作出反應的客戶端可能更難; 即使每個單獨的調用來自不同的線程,避免並發調用也是有幫助的。
  • 擁有一個強制執行某些“契約”的基礎/助手類非常有用。

如果人們能夠沿着這些方向展示更具體的建議,我會很好奇。

是的,yield關鍵字很可愛; 也許IObservable(OfT)會有類似的東西? [編輯:在Eric Meijer的PDC '09談話中,他說“是的,看這個空間”,以產生可觀察的聲明產量。

對於接近的東西(而不是自己動手),請查看“ (尚未)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);
}
  1. 裂開反射器並看看。

  2. 觀看一些C9視頻 - 這個視頻展示了如何'衍生'選擇'組合'

  3. 秘訣是創建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);
    }
}

我會讓你解決剩下的問題......這是一個非常好的理解練習。

這里有一個很好的小線程有相關的問題。

關於這個實現只有一個評論:

在.net fw 4中引入並發集合之后,最好使用ConcurrentDictioary而不是簡單的字典。

它節省了集合上的處理鎖。

ADI公司。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM