简体   繁体   中英

How to create repeatable IObservable of resource instances?

Need some help with RX. I want define observable, which should create resource when first subscribtion created, post this resource instance once for every new subscribtion, and when all subscribtion are done that resource instance must be disposed. Something like Observable.Using, but with Publish(value) and RefCount behaviour. All my attempts to express it using standard operators failed. This code does what i want, but I think there must be standart way to do it. I'm really don't want reinvent the wheel.

using System;
using System.Reactive.Linq;
using System.Reactive.Disposables;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            // this part is what i can't express in standart RX operators..
            Res res = null;
            RefCountDisposable disp = null;
            var @using = Observable.Create<Res>(obs =>
                {
                    res = res ?? new Res();
                    disp = disp == null || disp.IsDisposed ? new RefCountDisposable(res) : disp;
                    obs.OnNext(res);
                    return new CompositeDisposable(disp.GetDisposable(), disp, Disposable.Create(() => res = null));
                });
            // end
            var sub1 = @using.Subscribe(Print);
            var sub2 = @using.Subscribe(Print);

            sub1.Dispose();
            sub2.Dispose();

            sub1 = @using.Subscribe(Print);
            sub2 = @using.Subscribe(Print);

            sub1.Dispose();
            sub2.Dispose();
            Console.ReadKey();
        }

        static void Print(object o)
        {
            Console.WriteLine(o.GetHashCode());
        }

    }
    class Res : IDisposable
    {
        public Res()
        {
            Console.WriteLine("CREATED");
        }

        public void Dispose()
        {
            Console.WriteLine("DISPOSED");
        }
    }

}

Output:

CREATED
1111
1111
DISPOSED
CREATED
2222
2222
DISPOSED

My "best" attempt with standart operators:

var @using = Observable.Using(() => new Res(), res => Observable.Never(res).StartWith(res))
     .Replay(1)
     .RefCount();

and output is:

CREATED
1111
1111
DISPOSED
CREATED
1111  <-- this is "wrong" value
2222
2222
DISPOSED

Thank you!

ps. sorry for my poor english =(

After a little headache i'm finally realized that problem with Using.Replay.RefCount was that Replay internally calls Multicast with single ReplaySubject instance, but in my specific case I need Replay that recreates subject on every new first subscription. Through google I found RXX library and it's ReconnectableObservable was the answer. It uses subject factory instead of subject instance to recreate subject in every Connect call(original rxx code, simply without contracts):

internal sealed class ReconnectableObservable<TSource, TResult> : IConnectableObservable<TResult>
{
    private ISubject<TSource, TResult> Subject
    {
        get { return _subject ?? (_subject = _factory()); }
    }

    private readonly object _gate = new object();
    private readonly IObservable<TSource> _source;
    private readonly Func<ISubject<TSource, TResult>> _factory;

    private ISubject<TSource, TResult> _subject;
    private IDisposable _subscription;

    public ReconnectableObservable(IObservable<TSource> source, Func<ISubject<TSource, TResult>> factory)
    {
        _source = source;
        _factory = factory;
    }

    public IDisposable Connect()
    {
        lock (_gate)
        {
            if (_subscription != null)
                return _subscription;

            _subscription = new CompositeDisposable(
                _source.Subscribe(Subject),
                Disposable.Create(() =>
                {
                    lock (_gate)
                    {
                        _subscription = null;
                        _subject = null;
                    }
                }));

            return _subscription;
        }
    }

    public IDisposable Subscribe(IObserver<TResult> observer)
    {
        lock (_gate)
        {
            return Subject.Subscribe(observer);
        }
    }
}

and few extension methods:

public static class Ext
{
    public static IConnectableObservable<T> Multicast<T>(this IObservable<T> obs, Func<ISubject<T>> subjectFactory)
    {
        return new ReconnectableObservable<T, T>(obs, subjectFactory);
    }

    public static IConnectableObservable<T> ReplayReconnect<T>(this IObservable<T> obs, int replayCount)
    {
        return obs.Multicast(() => new ReplaySubject<T>(replayCount));
    }

    public static IConnectableObservable<T> PublishReconnect<T>(this IObservable<T> obs)
    {
        return obs.Multicast(() => new Subject<T>());
    }
}

using that code, now i'm able to do so:

var @using = Observable
             .Using(() => new Res(), _ => Observable.Never(_).StartWith(_))
             .ReplayReconnect(1) // <-- that's it!
             .RefCount();

Yahoo! It works as expected.

Thanks for all who answered! You pushed me in the right direction.

Try this:

var @using = Observable.Using(
        () => new Res(),
        res => Observable.Return(res).Concat(Observable.Never<Res>()))
    .Publish((Res)null)
    .RefCount()
    .SkipWhile(res => res == null);

The Concat prevents observers from auto-unsubscribing when the observable produces its only value.

If there is a way to do this using standard operators, I can't see it.

The problem is that there is no "cache values only if there are subscribers" option amonst the standard operators.

The Replay operator will cache the last value regardless of subscribers, and is the underlying cause of the "wrong" value you are seeing.

It highlights the fact that Using + Replay is a dangerous combination since it's emitted a disposed value.

I suspect that if someone did manage some wizardry with the standard operators, it wouldn't be as readable as your Observable.Create implementation.

I have used Observable.Create many times to create code that I am certain is more concise, readable and maintainable than an equivalent construction using standard operators.

My advice is that there is absolutely nothing wrong with using Observable.Create - wrap your code up in a nice factory method that accepts the resource and you're good to go. Here's my attempt at doing that, it's just a repackage of your code with thread safety added:

public static IObservable<T> CreateObservableRefCountedResource<T>(Func<T> resourceFactory)
    where T : class, IDisposable
{
    T resource = null;
    RefCountDisposable resourceDisposable = null;
    var gate = new object();

    return Observable.Create<T>(o =>
        {
            lock (gate)
            {
                resource = resource ?? resourceFactory();
                var disposeAction = Disposable.Create(() =>
                    {
                        lock (gate)
                        {
                            resource.Dispose();
                            resource = null;
                        }
                    });

                resourceDisposable = (resourceDisposable == null || resourceDisposable.IsDisposed)
                                         ? new RefCountDisposable(disposeAction)
                                         : resourceDisposable;

                o.OnNext(resource);
                return new CompositeDisposable(
                    resourceDisposable,
                    resourceDisposable.GetDisposable());
            }
        });
}

EDITED - Forgot to call resourceDisposable.GetDisposable()!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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