0

In reactive extensions we have

IObservable<T> Switch(this IObservable<IObservable<T>> This)

I would like an implementation of

IObserver<T> Switch(this IObservable<IObserver<T>> This)

which would switch the outgoing events to different observers but presented as a single observer.

bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217

2 Answers2

3

This version handles a couple of issues:

  • There's a race condition that can result in lost events. If the observer observes an event on one thread while the source observable produces a new observer on another thread, if you do not use any sort of synchronization, you could end up calling OnCompleted on the current observer on one thread just before the other thread calls OnNext on that same observer. This will result in the event being lost.

  • Related to the above, by default, observers are not thread-safe. You should never concurrent calls to an observer or you will violate a primary Rx contract. Without any locking, the subscriber might call OnCompleted on the currentObserver while another thread is calling OnNext on that same observer. Out of the box, this sort of thing can be solved by using a Synchronized Subject. But since we need synchronization also for the previous problem, we can just use a simple mutex.

  • We need a way to unsubscribe from the source observable. I'm supposing that when the resulting observer is completed (or errored), this is a good time to unsubscribe from source since our observer has been told to expect no more events.

Here's the code:

public static IObserver<T> Switch<T>(this IObservable<IObserver<T>> source)
{
    var mutex = new object();
    var current = Observer.Create<T>(x => {});
    var subscription = source.Subscribe(o =>
    {
        lock (mutex)
        {
           current.OnCompleted();
           current = o;
        }
    });

    return Observer.Create<T>(
        onNext: v =>
        {
            lock(mutex)
            {                
              current.OnNext(v);
            }
        },
        onCompleted: () =>
        {
             subscription.Dispose();
             lock (mutex)
             {
                 current.OnCompleted();
             }
        },
        onError: e =>
        {
             subscription.Dispose();
             lock (mutex)
             {
                 current.OnError(e);
             }
        });
}
Brandon
  • 38,310
  • 8
  • 82
  • 87
  • Do you really think Interlocked.CompareExchange is required? What race condition are you protecting from. Reference copy is atomic in .net. http://stackoverflow.com/questions/5816939/are-reference-assignment-and-reading-atomic-operations – bradgonesurfing Apr 16 '13 at 06:28
  • However disposing of the subscription looks to be required as you have added in. – bradgonesurfing Apr 16 '13 at 06:30
  • 2
    Actually I just realized CompareExchange is not enough. There are 2 race conditions: One is that your observer is likely observing events on a different thread than your observable is producing new observers. Without some sort of protection, when your observer observes an event, it might grab the old `currentObserver` just as it changed and send the event to the wrong observer. The other issue is it might send the event *after* you've completed the observer which means that event will get lost. I'll update my answer to use an actual lock. – Brandon Apr 16 '13 at 14:31
1
public static IObserver<T> Switch<T>(this IObservable<IObserver<T>> This)
{
    IObserver<T> currentObserver = Observer.Create<T>(x => { });

    This.Subscribe(o => { currentObserver.OnCompleted(); currentObserver = o; });


    return Observer.Create<T>
        ( onNext: v => currentObserver.OnNext(v)
        , onCompleted: () => currentObserver.OnCompleted()
        , onError: v => currentObserver.OnError(v));
}
bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
  • you are dropping out all previous observers. is it ok? – ie. Apr 15 '13 at 06:19
  • I call OnCompleted as I drop out the previous. It is the intention to drop out the previous observers. – bradgonesurfing Apr 15 '13 at 06:20
  • in you question it sounds like "switch the outgoing events to different observers" not (for example) "switch the outgoing events to the last observer". it is confusing a bit (at least for me) – ie. Apr 15 '13 at 06:22
  • It should be the dual of http://msdn.microsoft.com/en-us/library/hh229197%28v=vs.103%29.aspx – bradgonesurfing Apr 15 '13 at 06:27
  • This solution has race conditions around `currentObserver`. And it "leaks" the subscription to `This`. There doesn't seem to be a way to ever unsubscribe. Should `This` be unsubscribed if the observer sees a complete or error event? – Brandon Apr 15 '13 at 19:29
  • Reference assignment in .net is atomic but the subscription to This is problematic maybe. – bradgonesurfing Apr 16 '13 at 03:55