2

My goal is to have two subscribers from an observable but I am only interested in the Latest item in the event stream. I want others to be discarded. Consider this like a stock price screen that updates every 1 second and disregarding any intermediate values. Here's my code:

    var ob = Observable.Interval(TimeSpan.FromMilliseconds(100)) // fast event source
        .Latest().ToObservable().ToEvent();

    ob.OnNext += (l =>
                      {
                          Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                          Thread.Sleep(1000); // slow processing of events
                          Console.WriteLine("Latest: " + l);
                                                    });

    ob.OnNext += (l =>
        {
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(1000); // slow processing of events
            Console.WriteLine("Latest1: " + l);
            //  subject.OnNext(l);
        });

However as a result of above code, despite I attached two events (it doesn't matter even if you use the subscribe notation either) Only the first subscription is invoked periodically. Second one doesn't run at all. Why is that so ?

Onur Gumus
  • 1,389
  • 11
  • 27

2 Answers2

3

First I think your requirement is one of the following:

  1. You want to only get future values
  2. or you want to get the most recent value (if any) and any future values
  3. or you only want the most recent value (if any)
  4. or you only want to sample the ticks and get the value at each second
  5. or you have a slow consumer and you need perform load shedding (like in a GUI)

The code for 1)

var ob = Observable.Interval(TimeSpan.FromMilliseconds(1000)) // fast event source
    .Publish();
ob.Connect();

The code for 2)

var ob = Observable.Interval(TimeSpan.FromMilliseconds(1000)) // fast event source
    .Replay(1);
ob.Connect();    

The code for 3)

var ob = Observable.Interval(TimeSpan.FromMilliseconds(1000)) // fast event source
    .Replay(1);
ob.Connect();  
var latest = ob.Take(1); 

The code for 4) can be this, but there are subtle behaviors around what you consider a window.

var ob = Observable.Interval(TimeSpan.FromMilliseconds(200)) // fast event source
    .Replay(1);
//Connect the hot observable
ob.Connect();

var bufferedSource = ob.Buffer(TimeSpan.FromSeconds(1))
    .Where(buffer => buffer.Any())
    .Select(buffer => buffer.Last());  

The code for 5) can be found on James World's blog http://www.zerobugbuild.com/?p=192 and is quite common in many banking applications in London.

Lee Campbell
  • 10,631
  • 1
  • 34
  • 29
  • Thank you for the answer. Though, that's not my requirement. There is no 1 second buffering requirement. I want to get the latest value as soon as my subscriber code ended. The code I posted achieves this! However only for a single subscriber. I trying to figure out why it won't invoke the second one. I already read all relevant posts on internet (including your site and posts too). The above approach looked better to me with exception of inability to invoke the second subscriber. – Onur Gumus Feb 09 '16 at 08:25
  • Basically I want to ignore the intermediate values while the subscriber code is running.And none of the above achives that. – Onur Gumus Feb 09 '16 at 08:31
  • James' blog post does achieve that. You are blocking the thread with your Thread.Sleep. I believe that is preventing your other subscription from running. Once you are using Rx, you are well advised to avoid Thread.Sleeps – Lee Campbell Feb 09 '16 at 08:36
  • Yes, I agree though, I put thread.Sleeps just to emulate some work. – Onur Gumus Feb 09 '16 at 08:39
  • Did you try the `ObserveLatestOn` custom operator? – Lee Campbell Feb 10 '16 at 06:42
  • Yes I tried that as well. It only works as long as your scheduler is not CurrentThread. If you give current thread scheduler, it doesn't skip. Where as my approach above skips even with a single thread. – Onur Gumus Feb 10 '16 at 07:08
2

I don't think you understand what .Latest() does.

public static IEnumerable<TSource> Latest<TSource>(
    this IObservable<TSource> source
)

The enumerable sequence that returns the last sampled element upon each iteration and subsequently blocks until the next element in the observable source sequence becomes available.

Note that it blocks waiting for the next element from the observable.

So when you turned the IObservable<> into an IEnumerable<> using .Latest() you then had to use .ToObservable() to turn it back into an IObservable<> to be able to call .ToEvent(). That's where it fell over.

The problem with this code is that you're creating code that blocks.

If you just do it this, it works:

var ob = Observable.Interval(TimeSpan.FromMilliseconds(100)).ToEvent();

There's no need to call .Latest() as you're always getting the latest value from an observable. You can never get an earlier value. It's an observable, not a time machine.

What I also don't understand is why you are calling .ToEvent() in any case. what's the need?

Just do this:

var ob = Observable.Interval(TimeSpan.FromMilliseconds(100));

ob.Subscribe(l =>
{
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    Thread.Sleep(1000); // slow processing of events
    Console.WriteLine("Latest: " + l);
});

ob.Subscribe(l =>
{
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    Thread.Sleep(1000); // slow processing of events
    Console.WriteLine("Latest1: " + l);
});
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Let me explain: If I use Latest, then a lot of values that generated during the Sleep of subscription execution are skipped. So my output becomes like 9, 18, 27. This is what I want. if I don't use Latest, then I get values like 1,2,3 and none of the items are skipped. This second case is not what I want. As for the ToEvent, it is not necessary, I put them to show how it behaves inconsistently with C# event syntax. You attach two event handlers via += operator and only 1 of them invoked. This behavior is inconsistent. – Onur Gumus Feb 09 '16 at 11:28
  • 2
    @ReverseBlade - Just on the last point - the behaviour isn't inconsistent. The `.Latest().ToObservable().ToEvent()` chain creates a blocking operator that only returns a single output at a time and blocks the rest of the time. The second `OnNext` doesn't even get a chance to get a value. It's not inconsistent. It's how those operators work together. You should avoid mixing enumerables and observables like that. – Enigmativity Feb 09 '16 at 13:01
  • Ah yes. Actally I figured that out. Unfortunately this is what I want to do. I couldn't find a better way to achieve this. – Onur Gumus Feb 09 '16 at 14:21
  • @ReverseBlade - I've still thinking about how I would solve your problem. I'll see what I can come up with. – Enigmativity Feb 09 '16 at 20:52