繁体   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