4

I am learning .net Rx (Reactive Extensions) library and trying to create a proper Observable that read user input from Console.

So far I came to this:

    public static IObservable<string> ConsoleInputObservable()
    {
        return Observable.Create<string>(observer =>
        {
            var cancelable = new BooleanDisposable();
            while(!cancelable.IsDisposed)
            {
                observer.OnNext(Console.ReadLine());
            }
            observer.OnCompleted();
            return cancelable;
        });
    }

Unfortunately, this implementation has at least one problem - there is no way to unsubscribe of it.

So my question is: How to properly convert series of blocking events to Observable?

Thanks.

edit: typos

James World
  • 29,019
  • 9
  • 86
  • 120
Dmitrii
  • 321
  • 6
  • 17

1 Answers1

3

Here you go.

Note a few things:

  • This allows the user to supply an appropriate scheduler to control where concurrency is run, and yield each loop to prevent it getting too gummed up (although waiting on the Console is obviously pretty gummy anyway...)
  • We should not call OnCompleted in this example, since the only way to terminate is by cancelling the subscription - and you strive to send no further messages post cancellation
  • We also don't send an OnNext post cancellation here either.

Here is the code:

public static IObservable<string> ConsoleInputObservable(
    IScheduler scheduler = null)
{
    scheduler = scheduler ?? Scheduler.Default;
    return Observable.Create<string>(o =>
    {
        return scheduler.ScheduleAsync(async (ctrl, ct) =>
        {
            while(!ct.IsCancellationRequested)
            {                    
                var next = Console.ReadLine();
                if(ct.IsCancellationRequested)
                    return;

                o.OnNext(next);
                await ctrl.Yield();
            }
        });
    });
}

Addendum

@MartinLiversage commented that the behaviour with multiple subscribers would be undesirable - which prompted this addendum. You can simply Publish() the above code, but given the nature of the Console is that there is only one for an application and only one thread can be reading it at a time, a different approach is warranted.

I ignored this above since I felt the question was probably more about the threading aspect than the nature of Console. If you genuinely are interested in reporting lines entered at the Console, some kind of main loop like the following would probably be more practical - and this represents a reasonable use of Subject.

static void Main()
{
    Subject<string> sc = new Subject<string>();

    // kick off subscriptions here...
    // Perhaps with `ObserveOn` if background processing is required
    sc.Subscribe(x => Console.WriteLine("Subscriber1: " + x));
    sc.Subscribe(x => Console.WriteLine("Subscriber2: " + x));

    string input;
    while((input = Console.ReadLine()) != "q")
    {
        sc.OnNext(input);
    }
    sc.OnCompleted();

    Console.WriteLine("Finished");
}
James World
  • 29,019
  • 9
  • 86
  • 120
  • Thanks! Could you recommend any sources on "real-life" examples of Rx? I am about to finish [Lee Campel`s Introduction to Rx](http://www.introtorx.com/) and lookng for something advanced. – Dmitrii Apr 04 '15 at 14:36
  • Have a look at http://reactivetrader.com/ - put together by the team Lee works with. – James World Apr 04 '15 at 15:05
  • 2
    One issue to be aware is that if you subscribe several times to the same `IObservable` each line read from the user will only arrive at one subscription. E.g. with two subscriptions each subscriber will see every other line entered. – Martin Liversage Apr 06 '15 at 11:54
  • Yes, this is a Cold Observable, to make it hot, you can Publish() it. – James World Apr 06 '15 at 12:49