如何创建可重复的资源实例IObservable?

时间:2013-10-15 15:55:51

标签: c# system.reactive

需要RX的帮助。我想定义observable,它应该在创建第一个订阅时创建资源,为每个新订阅发布一次此资源实例,并且当完成所有订阅时,必须处理该资源实例。类似于Observable.Using,但具有Publish(value)和RefCount行为。我使用标准运算符表达它的所有尝试都失败了。这段代码做了我想要的,但我认为必须有标准的方法来做到这一点。我真的不想重新发明轮子。

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

}

输出:

CREATED
1111
1111
DISPOSED
CREATED
2222
2222
DISPOSED

我对标准操作员的“最佳”尝试:

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

,输出为:

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

谢谢!

PS。抱歉我的英语不好=(

3 个答案:

答案 0 :(得分:2)

稍微头痛之后我终于意识到Using.Replay.RefCount的问题是Replay内部调用MulticastReplaySubject个实例,但在我的具体情况下我需要Replay 在每个新的首次订阅中重新创建主题。通过谷歌,我找到了RXX库,它的ReconnectableObservable就是答案。它使用主题工厂而不是主题实例来重新创建每个Connect调用中的主题(原始rxx代码,简单地没有合同):

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

和一些扩展方法:

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

使用该代码,现在我可以这样做:

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

雅虎!它按预期工作。

感谢所有回答的人!你把我推向了正确的方向。

答案 1 :(得分:0)

试试这个:

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

当{oborable'产生唯一的值时,Concat会阻止观察者自动取消订阅。

答案 2 :(得分:0)

如果有办法使用标准运算符,我看不到它。

问题是标准运营商没有“只有订户”的“缓存值 ”选项。

无论订阅者如何,重放运算符都将缓存最后一个值,并且是您看到的“错误”值的根本原因。

它强调了这样一个事实:使用+重放是一种危险的组合,因为它发出了一个处置值。

我怀疑如果某人使用标准运算符管理了一些魔法,它就不会像Observable.Create实现那样可读。

我已经多次使用Observable.Create创建代码,我确信它比使用标准运算符的等效构造更简洁,可读和可维护。

我的建议是使用Observable.Create绝对没有问题 - 用一个接受资源的漂亮的工厂方法包装你的代码,你很高兴。这是我尝试这样做的,它只是重新打包代码并增加了线程安全性:

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

已编辑 - 忘记调用resourceDisposable.GetDisposable()!