2

I'm new to Rx and am absolutely loving it. I've found existing parts of our code that can be greatly simplified by using it. I've run into a performance issue and would be really grateful if there's a guru out there who could help me please.

Previously I had a manually implemented observer class that accepted subscriptions along with a key filter. When an event came into the class, it would use the key provided to look up which observers needed a callback. This is a very simplified version of the code:

 class OldClass
 {
    private Dictionary<string, List<Action<UsefulInfo>> _callbacks = 
        new Dictionary<string, List<Action<UsefulInfo>>();

    void Subscribe(string key, Action<UsefulInfo> callback)
    {
        _callbacks[key].Add(callback);
    }

    // Some event happens that we want to notify subscribers about
    void EventHandler(object sender, SomeEventArgs e)
    {
        // Create object for callbacks
        UsefulInfo info = CreateUsefulInfo(e);

        string key = info.Key;

        // Look up callbacks for key
        _callbacks[key].ForEach(callback => callback(info));
    }
 }

I've updated this to use Rx as follows:

class NewClass
{
    private Subject<UsefulInfo> _subject = new Subject<UsefulInfo>();
    private IObservable<UsefulInfo> _observable;

    public NewClass()
    {
        _observable = _subject.ToObservable();
    }

    IDisposable Subscribe(string key, Action<UsefulInfo> callback)
    {
        return _observable.Where(x => x.Key == key).Subscribe(callback);
    }

    // Some event happens that we want to notify subscribers about
    void EventHandler(object sender, SomeEventArgs e)
    {
        UsefulInfo info = CreateUsefulInfo(e);

        _observable.OnNext(info);
    }
 }

The old code does an O(1) dictionary key look-up to find the callbacks, but the new Rx code calls the Where Func O(n) times. I have thousands of observers.

Is there a way I can give Rx a Func that returns a key which it can then use internally to store the observer in a dictionary? Is there another way I could improve performance? Or am I using the framework in a way that was not intended?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
JamesD
  • 440
  • 6
  • 16

1 Answers1

2

You can share observers by using Publish on the Where'd sequence and then use RefCount on the IConnectableObservable to more intelligently manage subscriptions to the source.

In your scenario, I'd imagine you'd store these "published" observables in the dictionary and add to them on demand.

Here is a basic implementation:

class NewClass 
{ 
    private Subject<UsefulInfo> _subject = new Subject<UsefulInfo>(); 
    private IDictionary<string, IObservable<UsefulInfo>> _keyedObservables; 

    public NewClass() 
    { 
        _keyedObservables = new Dictionary<string, IObservable<UsefulInfo>>();
    } 

    IDisposable Subscribe(string key, Action<UsefulInfo> callback) 
    { 
        // NOT threadsafe for concurrent subscriptions!
        if (!_keyedObservables.Contains(key))
        {
            var keyedAndPublished = _subject.Where(x => x.Key == key)
                .Publish()
                .RefCount();

            _keyedObservables.Add(key, keyedAndPublished);
        }

        return _keyedObservables[key].Subscribe(callback);
    } 

    // Some event happens that we want to notify subscribers about 
    void EventHandler(object sender, SomeEventArgs e) 
    { 
        UsefulInfo info = CreateUsefulInfo(e); 

        _observable.OnNext(info); 
    } 
} 
Richard Szalay
  • 83,269
  • 19
  • 178
  • 237
  • Thanks for taking the time to answer this Richard. I'm very new to Rx, so would you mind expanding just a little please? – JamesD Jun 22 '12 at 03:28
  • @JamesD - I've added an implementation. [This question](http://stackoverflow.com/questions/2833904/iconnectableobservables-in-rx) might help explain IConnectableObservable further. – Richard Szalay Jun 22 '12 at 03:42
  • You're a genius, this makes complete sense. Thanks so much for your time. – JamesD Jun 22 '12 at 03:53
  • I've just pasted the code into a simple test project and run it up. It does indeed share observers per key. The problem is that most observers calling in will have different keys. So the Where func on the subject will still be called once for each key, which will be in the 1000s for every event. Does that make sense? – JamesD Jun 22 '12 at 04:15
  • @JamesD - Given that there are 1000s of unique keys, I don't think Rx can do what you're after using a single Subject. I'd recommend creating one Subject per key and store _that_ in a dictionary. – Richard Szalay Jun 22 '12 at 04:31
  • There would be 1000s of unique keys. It sounds like the original implementation is close to what I'd need, but it could be improved upon by using observables instead of storing the callback actions. Thanks again. – JamesD Jun 22 '12 at 06:20