2

I want to synchronize access to a BehaviorSubject<T>, so I'm looking to use Subject.Synchronize. However, I have a couple of pain points with this interface and am wondering whether I'm missing a more agreeable way of doing things.

Firstly, I am forced to store both the original subject and the synchronized subject. This is because I sometimes use the Value property on BehaviorSubject<T>. It's also because the return value of Synchronize is not disposable, so I must needs store an instance of the original subject in order to dispose it correctly.

Secondly, the return value of Synchronize is ISubject<TSource, TResult>, which is not compatible with ISubject<T>.

Thus I end up with code like this:

public class SomeClass
{
    private readonly BehaviorSubject<string> something;
    private readonly ISubject<string, string> synchronizedSomething;

    public SomeClass()
    {
        this.something = new BehaviorSubject<string>(null);

        // having to provide the string type here twice is annoying
        this.synchronizedSomething = Subject.Synchronize<string, string>(this.something);
    }

    // must remember to use synchronizedSomething here (I forgot and had to edit my question again, showing how easy it is to screw this up)
    public IObservable<string> Something => this.synchronizedSomething.AsObservable();

    // could be called from any thread
    public void SomeMethod()
    {
        // do some work

        // also must be careful to use synchronizedSomething here
        this.synchronizedSomething.OnNext("some calculated value");
    }

    public void Dispose()
    {
        // synchronizedSomething is not disposable, so we must dispose the original subject
        this.something.Dispose();
    }
}

Is there a cleaner/better approach that I'm missing? Just to be clear, what I would love to be able to do instead is something like this (pseudo code):

public class SomeClass
{
    private readonly IBehaviorSubject<string> something;

    public SomeClass()
    {
        this.something = new BehaviorSubject<string>(null).Synchronized();
    }

    public IObservable<string> Something => this.something.AsObservable();

    // could be called from any thread
    public void SomeMethod()
    {
        // do some work

        this.something.OnNext("some calculated value");
    }

    public void Dispose()
    {
        this.something.Dispose();
    }
}
Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
  • 1
    When you say synchronize access, do you mean you want have atomic writes? or do you mean that you want to have serialized reads (subscribers)? Can you extend your sample to 1) compile - you are missing the default value for your `BehaviorSubject` in the ctor, 2) expose how `SomeClass` would be used - you only have a ctor and a `Dispose` method and never expose the subjects. – Lee Campbell Nov 17 '15 at 12:55
  • @LeeCampbell: edited – Kent Boogaart Nov 17 '15 at 22:09

2 Answers2

2

I have some notes from the code sample you have posted

  1. IBehaviorSubject<string> isn't a type defined in Rx.NET. Maybe you mean ISubject<string>?
  2. You pass null as the default value to the BehaviorSubject<T>, often when I see this, the user actually just wanted ReplaySubject<string>(1). This depends on if you have a Where(x=>x!=null) or a Skip(1) as compensating behavior somewhere in your code base.
  3. Maybe you want to use the static method Subject.Synchronize(ISubject<T>) instead of the extension method .Synchronized()?

This might be a suitable replacement for your sample code above.

public class SomeClass
{
    //Exposed as ISubject as I can't see usage of `Value` and `TryGetValue` are not present.
    private readonly ISubject<string> something;

    public SomeClass()
    {
        var source = new BehaviorSubject<string>(null);
        //Maybe this is actually what you want?
        //var source = new ReplaySubject<string>(1);
        this.something = Subject.Synchronize(source);
    }

    public IObservable<string> Something => this.something.AsObservable();

    // could be called from any thread
    public void SomeMethod()
    {
        // do some work

        this.something.OnNext("some calculated value");
    }
}
Lee Campbell
  • 10,631
  • 1
  • 34
  • 29
  • Ugh, thanks but I _do_ need to use the `Value` property. I said this in my question but neglected to include it in the code sample. And the code is just pseduo-code. I'm aware there's no `IBehaviorSubject`, but I feel like I'd need one to be able to access `Value` against the synchronized subject. Also, your code doesn't deal with disposal. – Kent Boogaart Nov 18 '15 at 22:15
  • Ok, I am just trying to guess what your requirements are, as your sample code doesn't compile. – Lee Campbell Nov 19 '15 at 15:40
  • You don't show where you want synchronized access to your subject (or why). You want access to `Value`, but that doesn't need to be synchronized? Generally with Rx, you are best to serialize (with a scheduler) and not synchronize (with a lock). I am sorry I cant be of any help. – Lee Campbell Nov 19 '15 at 15:47
0
public class SynchronizeBehaviorSubject<T> : ISubject<T>, IDisposable
{
    private readonly BehaviorSubject<T> _source;
    private readonly ISubject<T> _sourceSync;

    public SynchronizeBehaviorSubject(BehaviorSubject<T> source)
    {
        _source = source;
        _sourceSync = source.Synchronize();
    }

    public void OnCompleted() => _sourceSync.OnCompleted();

    public void OnError(Exception error) => _sourceSync.OnError(error);

    public void OnNext(T value) => _sourceSync.OnNext(value);

    public IDisposable Subscribe(IObserver<T> observer) => _sourceSync.Subscribe(observer);

    public T Value => _source.Value;

    public bool HasObservers => _source.HasObservers;
    public void Dispose() => _source.Dispose();
    public bool IsDisposed => _source.IsDisposed;
}

public static class ReactiveEx
{

    public static ISubject<T> Synchronize<T>(this ISubject<T> source) =>
        Subject.Synchronize(source);

    public static SynchronizeBehaviorSubject<T> Synchronize<T>(this BehaviorSubject<T> source) =>
        new SynchronizeBehaviorSubject<T>(source);
}

Usage:

private readonly SynchronizeBehaviorSubject<bool> _isBool 
                    = new BehaviorSubject(false).Synchronize();

bool isBool = _isBool.Value;

or even ISubject<T> if you don't need to get the Value

shtse8
  • 1,092
  • 12
  • 20