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");
}