Using Rx to synchronize asynchronous events - c#

I want to put Reactive Extensions for .NET (Rx) to good use and would like to get some input on doing some basic tasks. To illustrate what I'm trying to do I have a contrived example where I have an external component with asyncronous events:
class Component {
public void BeginStart() { ... }
public event EventHandler Started;
}
The component is started by calling BeginStart(). This method returns immediately, and later, when the component has completed startup, the Started event fires.
I want to create a synchronous start method by wrapping the component and wait until the Started event is fired. This is what I've come up with so far:
class ComponentWrapper {
readonly Component component = new Component();
void StartComponent() {
var componentStarted =
Observable.FromEvent<EventArgs>(this.component, "Started");
using (var startedEvent = new ManualResetEvent(false))
using (componentStarted.Take(1).Subscribe(e => { startedEvent.Set(); })) {
this.componenet.BeginStart();
startedEvent.WaitOne();
}
}
}
I would like to get rid of the ManualResetEvent, and I expect that Rx has a solution. But how?

PL's answer if perfectly good for your spec, but I thought you might get better results by not fighting RX with .First() but embracing it with creating an observable to your component:
public static IObservable<Unit> AsObservable(this Component component)
{
return Observable.Defer(() =>
{
component.BeginStart();
return Observable
.FromEvent<EventArgs>(component, "Started")
.Select(_ => new Unit());
});
}
Then you could use it as blocking:
new Component().AsObservable().First();
Non - blocking:
new Component().AsObservable().Subscribe(_ => Console.WriteLine("Done"));
Hot:
var pub = new Component().AsObservable().Publish();
pub.Subscribe(_ => Console.WriteLine("Sub1"));
pub.Subscribe(_ => Console.WriteLine("Sub2"));
pub.Connect(); // started just once per two subscriptions
Composable:
new Component().AsObservable().Delay(TimeSpan.FromSeconds(1));
etc...
EDIT: For the case of multiple events that you have to wait on and collect information,
the following variation could be used:
public static IObservable<EventArgs> AsObservable(this Component component)
{
return Observable.Defer(() =>
{
component.BeginStart();
return
Observable.FromEvent<EventArgs>(component, "Started1").Take(1)
.Merge(
Observable.FromEvent<EventArgs>(component, "Started2").Take(1))
.Select(evt => evt.EventArgs);
});
}
With this one, if you want to block till completion, you might use .AsObservable.Last().

Something like this should do it:
var replay = Observable
.FromEvent<EventArgs>(this.component, "Started")
.Replay();
replay.Connect();
component.BeginStart();
replay.First();

Related

How to make an IObservable<string> from console input

I have tried to write console observable as in the example below, but it doesn't work. There are some issues with subscriptions. How to solve these issues?
static class Program
{
static async Task Main(string[] args)
{
// var observable = Observable.Interval(TimeSpan.FromMilliseconds(1000)).Publish().RefCount(); // works
// var observable = FromConsole().Publish().RefCount(); // doesn't work
var observable = FromConsole(); // doesn't work
observable.Subscribe(Console.WriteLine);
await Task.Delay(1500);
observable.Subscribe(Console.WriteLine);
await new TaskCompletionSource().Task;
}
static IObservable<string> FromConsole()
{
return Observable.Create<string>(async observer =>
{
while (true)
{
observer.OnNext(Console.ReadLine());
}
});
}
}
If I used Observable.Interval, it subscribes two times and I have two outputs for one input. If I used any version of FromConsole, I have one subscription and a blocked thread.
To start with, it is usually best to avoid using Observable.Create to create observables - it's certainly there for that purpose, but it can create observables that don't behave like you think they should because of their blocking nature. As you've discovered!
Instead, when possible, use the built-in operators to create observables. And that can be done in this case.
My version of FromConsole is this:
static IObservable<string> FromConsole() =>
Observable
.Defer(() =>
Observable
.Start(() => Console.ReadLine()))
.Repeat();
Observable.Start effectively is like Task.Run for observables. It calls Console.ReadLine() for us without blocking.
The Observable.Defer/Repeat pair repeatedly calls Observable.Start(() => Console.ReadLine()). Without the Defer it would just call Observable.Start and repeatedly return the one string forever.
That solves that.
Now, the second issue is that you want to see the value from the Console.ReadLine() output by both subscriptions to the FromConsole() observable.
Due to the way Console.ReadLine works, you are getting values from each subscription, but only one at a time. Try this code:
static async Task Main(string[] args)
{
var observable = FromConsole();
observable.Select(x => $"1:{x}").Subscribe(Console.WriteLine);
observable.Select(x => $"2:{x}").Subscribe(Console.WriteLine);
await new TaskCompletionSource<int>().Task;
}
static IObservable<string> FromConsole() =>
Observable
.Defer(() =>
Observable
.Start(() => Console.ReadLine()))
.Repeat();
When I run that I get this kind of output:
1:ddfd
2:dfff
1:dfsdfs
2:sdffdfd
1:sdfsdfsdf
The reason for this is that each subscription starts up a fresh subscription to FromConsole. So you have two calls to Console.ReadLine() they effectively queue and each one only gets each alternate input. Hence the alternation between 1 & 2.
So, to solve this you simply need the .Publish().RefCount() operator pair.
Try this:
static async Task Main(string[] args)
{
var observable = FromConsole().Publish().RefCount();
observable.Select(x => $"1:{x}").Subscribe(Console.WriteLine);
observable.Select(x => $"2:{x}").Subscribe(Console.WriteLine);
await new TaskCompletionSource<int>().Task;
}
static IObservable<string> FromConsole() =>
Observable
.Defer(() =>
Observable
.Start(() => Console.ReadLine()))
.Repeat();
I now get:
1:Hello
2:Hello
1:World
2:World
In a nutshell, it's the combination of the non-blocking FromConsole observable and the use of .Publish().RefCount() that makes this work the way you expect.
The problem is that the Console.ReadLine is a blocking method, so the subscription to the FromConsole sequence blocks indefinitely, so the await Task.Delay(1500); line is never reached. You can solve this problem by reading from the console asynchronously, offloading the blocking call to a ThreadPool thread:
static IObservable<string> FromConsole()
{
return Observable.Create<string>(async observer =>
{
while (true)
{
observer.OnNext(await Task.Run(() => Console.ReadLine()));
}
});
}
You can take a look at this question about why there is no better solution than offloading.
As a side note, subscribing to a sequence without providing an onError handler is not a good idea, unless having the process crash with an unhandled exception is an acceptable behavior for your app. It is especially problematic with sequences produced with Observable.Create<T>(async, because it can lead to weird/buggy behavior like this one: Async Create hanging while publishing observable.
You need to return a observable without the publish. You can then subscribe to it and do your thing further. Here is an example. When I run it i can readline multiple times.
public class Program
{
static void Main(string[] args)
{
FromConsole().Subscribe(x =>
{
Console.WriteLine(x);
});
}
static IObservable<string> FromConsole()
{
return Observable.Create<string>(async observer =>
{
while (true)
{
observer.OnNext(Console.ReadLine());
}
});
}
}

Exception handling in RX.Net when using ToEventPattern and Timeout

I am writing some code using RX in C# that must interface with an older system by emitting events.
In summary, I have an observable and need to emit one event when the observable completes and another event if a timeout exception is detected. The main problem is how best to handle the exception.
I'm relatively new to RX, so although I have found a solution, I can't be sure that there isn't a better or more appropriate way that uses the RX extensions better.
This is not the real code but indicates the pattern of my thinking:
public delegate void SuccessHandler(object sender, SuccessEventArgs e);
public event SuccessHandler OnSuccess;
public delegate void TimeoutHandler(object sender, TimeoutEventArgs e);
public event TimeoutHandler OnTimeout;
var id;
var o = Observable.Return() // <- this would be a fetch from an asynchronous source
.Where(r=>r.status=="OK")
.Timeout(new Timespan(0,0,30)
.Do(r=> {
id=r.Id // <-- Ugh! I know this shouldn't be done!
}
.Subscribe(r => {
var statusResponse= new StatusResponse()
{
Id = r.Id
Name = r.Name
Message = "The operation completed successfully",
Status = Status.Success
};
if (OnSuccess == null) return;
OnSuccess (this, new SuccessEventArgs(statusResponse);
},
e =>
{
_logger.LogError(e, "A matching response was not returned in a timely fashion");
if (OnTimeout == null) return;
OnTimeout(this, new TimeoutEventArgs(id));
});
If I didn't need to detect and act upon the timeout it would be fine; I have already worked out how to substitute the Subscribe for ToEventPattern:
...
.Select(r =>
{
var statusResponse= new StatusResponse()
{
Id = r.Id
Name = r.Name
Message = "The operation completed successfully",
Status = Status.Success
};
return new EventPattern<SuccessEventArgs>(this, new SuccessEventArgs(statusResponse));
})
.ToEventPattern();
However, I'd like to be able to detect the timeout (and possibly other exceptions). my experiments with Catch have been unsuccessful because I can't seem to get the types to line up correctly, probably because I don't really understand what is going on.
I'd very much appreciate opinions on this. Is this an acceptable solution? How can I improve it? Can anyone point me to some good online references that will explain how this kind of flow-control and exception handling can be done (all the examples I've seen so far seem to stop short of the real-world case where you want to emit an event and combine that with exception handling).
Thanks in advance
You can branch from observables quite easily, e.g.
var a = Observable.Range(0, 10);
var b = a.Select(x => x * x);
var c = a.Select(x => x * 10);
A word of warning - if the observable is cold, this will cause the producer function to run for each subscription. Look up the difference between hot and cold observables if this isn't clear.
I've created a solution that creates two branches from the source observable and turns each into an event:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var service = new Service();
var apiCall = service.CallApi();
apiCall.OnSuccess.OnNext += (_, __) => Console.WriteLine("Success!");
apiCall.OnTimeout.OnNext += (_, __) => Console.WriteLine("Timeout!");
Console.ReadLine();
}
}
class SuccessEventArgs{}
class TimeoutEventArgs{}
class ApiCall
{
public IEventPatternSource<SuccessEventArgs> OnSuccess {get;}
public IEventPatternSource<TimeoutEventArgs> OnTimeout {get;}
public ApiCall(IEventPatternSource<SuccessEventArgs> onSuccess, IEventPatternSource<TimeoutEventArgs> onTimeout)
{
OnSuccess = onSuccess;
OnTimeout = onTimeout;
}
}
class Service
{
public ApiCall CallApi()
{
var apiCall = Observable
.Timer(TimeSpan.FromSeconds(3))
.Do(_ => Console.WriteLine("Api Called"))
.Select(_ => new EventPattern<SuccessEventArgs>(null, new SuccessEventArgs()))
// .Timeout(TimeSpan.FromSeconds(2)) // uncomment to time out
.Timeout(TimeSpan.FromSeconds(4))
// the following two lines turn the "cold" observable "hot"
// comment them out and see how often "Api Called" is logged
.Publish()
.RefCount();
var success = apiCall
// ignore the TimeoutException and return an empty observable
.Catch<EventPattern<SuccessEventArgs>, TimeoutException>(_ => Observable.Empty<EventPattern<SuccessEventArgs>>())
.ToEventPattern();
var timeout = apiCall
.Materialize() // turn the exception into a call to OnNext rather than OnError
.Where(x => x.Exception is TimeoutException)
.Select(_ => new EventPattern<TimeoutEventArgs>(null, new TimeoutEventArgs()))
.ToEventPattern();
return new ApiCall(success, timeout);
}
}

Observable (and cancelable) loop

I'm creating an emulator. The core of the emulation runs in an infinite loop like this:
while (true)
{
UpdateMachineState();
}
I would like to introduce Reactive Extensions to execute this loop into another thread and to make it cancelable, but I'm completely lost.
Since my emulator is a GUI application (Universal Windows), I don't wan't to block the UI thread.
It should look like:
...
while (true)
{
if (machine.IsHalted)
{
observer.OnCompleted;
}
observer.OnNext(machine.GetState());
cancellationToken.ThrowIfCancellationRequested();
}
...
The created sequence would eventually complete when the emulator enters the "halted" state. Otherwise, it will keep pushing States (an object that represents its internal state) forever.
I've tried with Observable.Create, but the overload that provides a CancellationToken requires a Task<Action>.
Here's how you do it in Rx:
void Main()
{
var scheduler = new EventLoopScheduler();
var loop = scheduler.Schedule(a =>
{
UpdateMachineState();
a();
});
Thread.Sleep(1);
loop.Dispose();
}
public void UpdateMachineState()
{
Console.Write(".");
}
The overload on .Schedule that I used takes a Action<Action> as the parameter. You simply call the inner action if you want the the action to be rescheduled - so the above code effectively creates the infinite loop.
You then call .Dispose() on the return from the .Schedule call to cancel the loop.
Another alternative is to use the .Generate operator:
var scheduler = new EventLoopScheduler();
var query =
Observable
.Generate(0, x => true, x => x, x => machine.GetState(), scheduler);
var subscription = query.Subscribe(x => Console.Write("."));
Thread.Sleep(1);
subscription.Dispose();

ActionBlock Framework 4 rx alternative

I'm interested in an ActionBlock implementation for Framework 4.0, since there it seems that TPL.Dataflow isn't supported for Framework 4.0.
More particularly, I'm interested in the case of the constructor that receives the Func<TInput, Task> delegate and the MaxDegreeOfParallism = 1 case.
I thought about implementing it using reactive extensions, but I'm not sure how to do it. Thought about creating a Subject<TInput> and calling OnNext on Post, and using SelectMany and task ToObservable stuff, but I'm not sure what to do with the scheduler. Here is a draft of what I was thinking of.
public class ActionBlock<TInput>
{
private readonly TaskCompletionSource<object> mCompletion = new TaskCompletionSource<object>();
private readonly Subject<TInput> mQueue = new Subject<TInput>();
public ActionBlock(Func<TInput, Task> action)
{
var observable =
from item in mQueue
from _ in action(item).ToObservable()
select _;
observable.Subscribe(x => { },
OnComplete);
}
private void OnComplete()
{
mCompletion.SetResult(null);
}
public void Post(TInput input)
{
mQueue.OnNext(input);
}
public Task Completion
{
get
{
return mCompletion.Task;
}
}
public void Complete()
{
mQueue.OnCompleted();
}
}
I thought maybe using EventLoopScheduler but I'm not sure it fits here since this is async.
Any ideas?
mQueue
.Select(input => Observable.FromAsync(() => action(input))
.Merge(maxDegreeOfParallelism)
.Subscribe(...);
If indeed maxDegreeOfParallelism is always 1, then just use Concat instead of Merge:
mQueue
.Select(input => Observable.FromAsync(() => action(input))
.Concat()
.Subscribe(...);
This works because FromAsync just creates a cold observable that will not run the async action until it is subscribed. We then use the maxConcurrency parameter of Merge (or just Concat) to limit the number of concurrent subscriptions (and thus the number of async actions running).
Edit:
And since your goal is to just have a Task that represents the completion of the stream, you can use ToTask instead of directly subscribing. ToTask will subscribe and return a Task with the final value. Because ToTask will throw if the observable does not produce a value, we'll use Count to guarantee it produces a value:
// task to mark completion
private readonly Task mCompletion;
// ...
this.mCompletion = mQueue
.Select(input => Observable.FromAsync(() => action(input))
.Concat()
.Count()
.ToTask();

Rx .net Subscribe() and EventPattern oddity

I'm using Rx in conjunction with a third-party API that uses the EventPattern. In this API you register your event handlers on the object and then invoke a method, StartWatching(), on the object that starts the events to begin triggering. I am using Observable.FromEventPattern to bridge the API in the Rx world but I am running into very odd problems where subscriptions will only work if they are called right by the invocation of StartWatching(). Below is a reduced case of what I am seeing.
This works:
foreach (var iq in interactionQueues)
{
Observable.FromEventPattern(iq, "TheEvent")
.Subscribe(e => Log.Info("I got called!"),
e => Log.Info("Error!", e),
() => Console.WriteLine("Seq completed!"));
iq.StartWatching();
}
If I call the Subscribe() and StartWatching() in different loops it stops working:
foreach (var iq in interactionQueues)
Observable.FromEventPattern(iq, "TheEvent")
.Subscribe(e => Log.Info("I got called!"),
e => Log.Info("Error!", e),
() => Console.WriteLine("Seq completed!"));
foreach (var iq in interactionQueues)
iq.StartWatching();
My only thought as to why this may happen is that the Observing or Subscribing is happening on the wrong thread. I have tried using Scheduler.CurrentThread and Scheduler.Immediate with SubscribeOn and ObserveOn but that didn't help. Any other ideas? Should I try a different Scheduler or is that a red herring?
Let's wrap this in a more friendly method:
public static TheEventArgs WatchEvent(this InteractionQueue this)
{
var ret = Observable.Create<TheEventArgs>(subj => {
// This entire block gets called every time someone calls Subscribe
var disp = new CompositeDisposable();
// Subscribe to the event
disp.Add(Observable.FromEventPattern(iq, "TheEvent").Subscribe(subj));
// Stop watching when we're done
disp.Add(Disposable.Create(() => iq.StopWatching());
iq.StartWatching();
// This is what to Dispose on Unsubscribe
return disp;
});
// When > 1 person Subscribes, only call the block above (i.e. StartWatching) once
return ret.Multicast(new Subject<TheEventArgs>()).RefCount();
}

Categories