簡體   English   中英

C#Rx如何在創建的Observable中正確處理源Enumerable

[英]C# Rx How to properly dispose of source Enumerable in created Observable

我想將一個IEnumerable,IDisposable(源)改編成一個Observable,並希望知道這樣做的最佳方法,並在取消訂閱時調用source.Dispose方法。

在introtorx.com上有一個關於調整IEnumerable的例子 ,但它明確指出它有許多缺點,例如錯誤的處理模式,糟糕的並發模型,沒有錯誤處理等等......內置版本處理這些缺點。 但內置版本似乎沒有在取消訂閱時調用源IEnumerable上的Dispose。

理想情況下,我想使用.Publish().RefCount()模式在同一個源上擁有多個訂閱者,並且只有在取消訂閱時才調用源Dispose()

這是我嘗試的代碼,雖然它不起作用。

static void FromEnumerableTest() {
    var observable = Observable.Create<int>(
        observer => {
            var source = new JunkEnumerable();
            foreach (int i in source) {
                observer.OnNext(i);
            }
            return () => {
                source.Dispose();
            };
        })
        .SubscribeOn(Scheduler.Default)
        .Do(i => Console.WriteLine("Publishing {0}", i))    // side effect to show it is running
        .Publish()
        .RefCount();

    //var observable = Observable.ToObservable(new JunkEnumerable())
    //    .SubscribeOn(Scheduler.Default)
    //    .Do(i => Console.WriteLine("Publishing {0}", i))    // side effect to show it is running
    //    .Publish()
    //    .RefCount();

    Console.WriteLine("Press any key to subscribe");
    Console.ReadKey();

    var subscription = observable.Subscribe(i => Console.WriteLine("subscription : {0}", i));
    Console.WriteLine("Press any key to unsubscribe");
    Console.ReadKey();
    subscription.Dispose();

    Console.WriteLine("Press any key to exit");
    Console.ReadKey();
}


class JunkEnumerable : IEnumerable<int>, IDisposable {
    public void Dispose() { Console.WriteLine("JunkEnumerable.Dispose invoked"); }

    public IEnumerator<int> GetEnumerator() { return new Enumerator(); }

    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

    class Enumerator : IEnumerator<int> {
        private int counter = 0;
        public int Current {
            get {
                Thread.Sleep(1000);
                return counter++;
            }
        }

        object IEnumerator.Current { get { return Current; } }

        public void Dispose() { Console.WriteLine("JunkEnumerable.Enumerator.Dispose invoked"); }

        public bool MoveNext() { return true; }

        public void Reset() { counter = 0; }
    }
}

Rx訂閱生命周期分為三個階段:

  1. 訂閱
  2. 意見
  3. 退訂

如果訂閱永遠不會完成,則不會發生取消訂閱代碼。 畢竟,如果您從未完全訂閱,為什么還需要取消訂閱? 您的示例代碼在訂閱代碼中具有無限循環,因此它永遠不會完成,因此取消訂閱代碼永遠不會發生。

處理IDisposable的常規方法是使用Observable.Using 處理IEnumerable的常規方法是使用.ToObservable 如果您嘗試將異步引入同步,可枚舉的代碼(如您的示例),則可以按如下方式執行:

var observable = Observable.Using(() => new JunkEnumerable(), junk => 
    Observable.Generate(junk.GetEnumerator(), e => e.MoveNext(), e => e, e => e.Current, e => TimeSpan.FromMilliseconds(20))
);

只要TimeSpan大於15毫秒,Rx就會將其變為異步,完成訂閱。 隨后的值是觀察階段的一部分,並且將完全取消訂閱。

這是一個在指定的調度程序上運行枚舉的運算符。 我們安排可枚舉的每個枚舉,以便一次性用品可以正確返回。

    public static IObservable<T> ToObservableOn<T>(this IEnumerable<T> source, IScheduler scheduler = default(IScheduler))
    {
        scheduler = scheduler ?? Scheduler.Default;
        return Observable.Create<T>(
            (observer) =>
            {
                var disposed = new BooleanDisposable();
                var enumerator = source.GetEnumerator();

                Action scheduleNext = default(Action);
                scheduleNext = () =>
                {
                    if (disposed.IsDisposed)
                        return;

                    if (!enumerator.MoveNext())
                    {
                        observer.OnCompleted();
                        return;
                    }

                    observer.OnNext(enumerator.Current);

                    scheduler.Schedule(scheduleNext);
                };

                scheduler.Schedule(scheduleNext);
                return StableCompositeDisposable.Create(disposed, enumerator);
            });
    }

從您的示例中,我們只需將SubscribeOn更改為:

        var observable = 
            new JunkEnumerable()
            .ToObservableOn(Scheduler.Default)                
            .Do(i => Console.WriteLine("Publishing {0}", i))    // side effect to show it is running
            .Publish()
            .RefCount();

暫無
暫無

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

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