2

I want to create an Observable that keeps pushing a list of values every t seconds.

For example, given the {1, 2, 3, 4} subscribers should get this:

1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2...

class Program
{
    static void Main()
    {
        var observable = Observable
            .Interval(TimeSpan.FromSeconds(3))
            .Zip(Observable.Range(1, 4)
            .Repeat(), (_, count) => count);

        observable.Subscribe(Console.WriteLine);

        Console.WriteLine("Finished!");
    }
}
  • I have worked on this example and it seems to work, but with a very nasty problem: the Main method never ends its execution! Why? :(

  • And even worse, after some minutes, this console application throws an OutOfMemoryException!

SuperJMN
  • 13,110
  • 16
  • 86
  • 185

4 Answers4

6

this seems like a simple mis-placed .Repeat() to me.

class Program
{
    static void Main()
    {
        var observable = Observable
            .Interval(TimeSpan.FromSeconds(3))
            .Zip(Observable.Range(1, 4), (_, count) => count)
            .Repeat();

        observable.Subscribe(Console.WriteLine);

        Console.WriteLine("Finished!");
        Console.ReadLine();
    }
}

This will now:

  • Not block the console from finishing
  • Not throw an OutOfMemoryException.

Note, no use of .Do(), no custom extension methods, no endless yielding of IEnumerables that block the thread ;-)

...and an alternate implementation without Zip so that hopefully a layman dev could read and understand (with disposal too!):

class Program
{
    static void Main()
    {
        var observable = Observable
            .Interval(TimeSpan.FromSeconds(3))
            //.Zip(Observable.Range(1, 4), (_, count) => count)
            .Select(i=>i+1)
            .Take(4)
            .Repeat();

        using (observable.Subscribe(Console.WriteLine))
        {
            Console.WriteLine("Running...");
            Console.ReadLine();
        }
        Console.WriteLine("Finished!");
    }
}
Lee Campbell
  • 10,631
  • 1
  • 34
  • 29
5

I've removed my suggested answer, as both Timothy & Lee's answers use built-in Rx functions and are far more elegant. I will leave the explanation of the issue, though, as I believe it's useful:

Observables are expected to be push sequences, and Zip will queue items from the faster producing stream while it waits for a value from the second to pair with the next. As Obsevable.Range returns these values as quick as the subscriber can handle, this fills all your memory and blocks the thread.

Charles Mager
  • 25,735
  • 2
  • 35
  • 45
  • OK, that is NICE! I've replaced the variable `observable` by your observable, but the Main method never completes. Why? – SuperJMN Jan 18 '16 at 09:30
  • Watch out, I've left the console application printing numbers for some minutes and it ends up throwing an OutOfMemoryException :( I don't underestand why. – SuperJMN Jan 18 '16 at 09:36
  • 1
    Ah, yes. I think `Zip` will basically run a queue of items for one observable while it waits for something from the second to pair it with. These are paired once a second, but the repeated observable will just fly and stuff the queue with enough integers from 1-4 that it'll use all your memory! I'll come up with an alternative... – Charles Mager Jan 18 '16 at 09:51
  • [This GitHub issue](https://github.com/Reactive-Extensions/Rx.NET/issues/19) covers the problem this shows. – Charles Mager Jan 18 '16 at 10:05
  • I'm not sure there's a magic Rx-solution here - the idea is observables are push-based and essentially we're wanting the infinite sequence to be pull-based. I suggest you revert to the original solution of an infinite enumerable and use the timer to get the next value. – Charles Mager Jan 18 '16 at 10:17
  • @CharlesMager There most definitely is an Rx magic solution here. See my answer. :) – Timothy Shields Jan 19 '16 at 00:11
  • @CharlesMager I think you ought to consider modifying your answer because that last code block is not good. I'm not saying it to be mean, I just want to make sure people aren't recreating things that are already built in. :) – Timothy Shields Jan 19 '16 at 00:19
  • @CharlesMager Your analysis of why the original code was having problems and running out of memory was spot on by the way. :) – Timothy Shields Jan 19 '16 at 00:21
  • @TimothyShields No offence taken. I'd agree the solution I came up with, while it works, is pretty ugly. Who knew `Zip` had an `IEnumerable` overload? Every day a school day. I'll amend my answer just to leave the explanation of the issue. – Charles Mager Jan 19 '16 at 08:14
  • thanks @CharlesMager, your explanation for the memory issue is spot on. – Lee Campbell Jan 20 '16 at 01:07
2

Charles Mager's answer already addressed why your current code isn't working and suggested one way of fixing it. Here's the simplest way I can think of fixing it:

    var observable = Observable.Zip(
        Observable.Interval(TimeSpan.FromSeconds(3)),
        Enumerable.Range(1, 4).Repeat(),
        (_, count) => count);

This is just using the version of Zip that combines an IObservable and an IEnumerable.

The Repeat extension method is defined as follows (same as Charles Mager's answer):

public static IEnumerable<T> Repeat(this IEnumerable<T> source)
{
    while (true)
    {
        foreach (var item in source)
        {
            yield return item;
        }
    }
}
Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
-1

In the System.Reactive.Concurrency namespace there are some classes and methods to help with scheduling.

The following code is a crude example of printing out the contents of an integer array every 3 seconds:

var numbers = new int[] { 1,2,3,4 };

var scs = new SynchronizationContextScheduler(new SynchronizationContext());
scs.SchedulePeriodic<int[]>(numbers, TimeSpan.FromSeconds(3), (n) => 
{
    foreach (var number in n)
    {
        Console.Write(number + " ");
    }
});

Console.ReadLine();

I'm not sure if this is what you are looking for but I hope it helps.

Brad
  • 4,493
  • 2
  • 17
  • 24