简体   繁体   English

C#Rx如何在创建的Observable中正确处理源Enumerable

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

I would like to adapt an IEnumerable,IDisposable (source) into an Observable and would like to know the best way to do this and have the source.Dispose method get called upon unsubscribe. 我想将一个IEnumerable,IDisposable(源)改编成一个Observable,并希望知道这样做的最佳方法,并在取消订阅时调用source.Dispose方法。

There is an example on introtorx.com of adapting an IEnumerable, but it explicitly states that it has many shortcomings such as incorrect disposal pattern, poor concurrency model, no error handling, etc... and that the built in version handles these. 在introtorx.com上有一个关于调整IEnumerable的例子 ,但它明确指出它有许多缺点,例如错误的处理模式,糟糕的并发模型,没有错误处理等等......内置版本处理这些缺点。 But the built in version doesn't seem to call Dispose on the source IEnumerable upon unsubscription. 但内置版本似乎没有在取消订阅时调用源IEnumerable上的Dispose。

Ideally I'd like to use the .Publish().RefCount() pattern to have multiple subscribers on the same source and only have the source Dispose() called when they are all unsubscribed. 理想情况下,我想使用.Publish().RefCount()模式在同一个源上拥有多个订阅者,并且只有在取消订阅时才调用源Dispose()

Here are is the code for my attempt, though it's not working. 这是我尝试的代码,虽然它不起作用。

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

There are three stages in an Rx subscription-lifetime: Rx订阅生命周期分为三个阶段:

  1. Subscription 订阅
  2. Observation 意见
  3. Unsubscription 退订

If the subscription never completes, the unsubscription code doesn't happen. 如果订阅永远不会完成,则不会发生取消订阅代码。 After all, if you never fully subscribed, why should you need to unsubscribe? 毕竟,如果您从未完全订阅,为什么还需要取消订阅? Your sample code has an infinite loop in the subscription code, so it never completes, so the unsubscription code will never happen. 您的示例代码在订阅代码中具有无限循环,因此它永远不会完成,因此取消订阅代码永远不会发生。

The normal way to handle an IDisposable is with Observable.Using . 处理IDisposable的常规方法是使用Observable.Using The normal way to handle an IEnumerable is with .ToObservable . 处理IEnumerable的常规方法是使用.ToObservable If you're trying to introduce asynchrony to synchronous, enumerable code (like your example), you can do so as follows: 如果您尝试将异步引入同步,可枚举的代码(如您的示例),则可以按如下方式执行:

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

As long as the TimeSpan is greater than 15 millis, Rx will turn it async, completing the subscription. 只要TimeSpan大于15毫秒,Rx就会将其变为异步,完成订阅。 The subsequent values are part of the observation stage, and unsubscription will fully take place. 随后的值是观察阶段的一部分,并且将完全取消订阅。

Here's an operator to run the enumeration on a specified scheduler. 这是一个在指定的调度程序上运行枚举的运算符。 We schedule each enumeration of the enumerable so that the disposables can correctly return. 我们安排可枚举的每个枚举,以便一次性用品可以正确返回。

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

From your example, we just change the SubscribeOn to: 从您的示例中,我们只需将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