How to dynamically delay observable over time - c#

I currently have a feature that users Timer() to fire an observable immediately and then every x millseconds after.
HoldPayloads = Observable.Merge(
EnumeratedSymbolKeys.Select(
o => Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonDown += h,
h => o.PreviewMouseLeftButtonDown -= h)
.Select(_ => Observable.Timer(DateTimeOffset.Now, TimeSpan.FromMilliseconds(o.Frequency))
.TakeUntil(Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonUp += h,
h => o.PreviewMouseLeftButtonUp -= h)))
.Switch()
.Select(_ => o.Payload)));
What I'd like to have is The observable fire immediately when a button is clicked, then after initial longer delay start to repeat at either a faster interval or a decreasing interval down to a limit, something like this:
--x------x--x--x--x--x--x-->
or
--x------x----x---x--x-x-x->
I attempted to use Delay() combined with Scan() to generate exponentially lower values to delay by but couldn't get it to work. Was I on the right track? Any better ways to do something like this?
Revised code using the answer from Shlomo:
HoldPayloads = Observable.Merge(
EnumeratedSymbolKeys.Select(
o => Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonDown += h,
h => o.PreviewMouseLeftButtonDown -= h)
.Select(_ => Observable.Generate(
1,
q => true,
i => i+1,
i => i,
i => i==1
? TimeSpan.FromMilliseconds(0)
: i > 10
? TimeSpan.FromMilliseconds(50)
: TimeSpan.FromMilliseconds(500/i))
.TakeUntil(Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonUp += h,
h => o.PreviewMouseLeftButtonUp -= h)))
.Switch()
.Select(_ => o.Payload)));
Ended up modifying the conditional so that the first item was immediate.

Observable.Generate is your friend (discussed well on this page).
For increasingly frequent values, replace .Select(_ => Observable.Timer(DateTimeOffset.Now, TimeSpan.FromMilliseconds(o.Frequency)) with something like this:
Observable.Generate(
1,
_ => true,
i => i + 1,
i => i,
i => i > 10
? TimeSpan.FromMilliseconds(50)
: TimeSpan.FromMilliseconds(500 / i)
)
Whatever your pattern is, fit it into that last parameter. Think of Generate as like a reactive for loop with all the dials available to you.
EDIT:
With the code above, the first item from Generate will be delayed. If you want the first item immediately, then the second item delayed, you can do as follows:
Observable.Generate(
1,
_ => true,
i => i + 1,
i => i,
i => i > 10
? TimeSpan.FromMilliseconds(50)
: TimeSpan.FromMilliseconds(500 / i)
)
.StartWith(0)
So the original .Select(_ => Observable.Timer(DateTimeOffset.Now, TimeSpan.FromMilliseconds(o.Frequency) is now .Select(_ => Observable.Generate(...).StartWith(0))

I just thought I'd just add an answer to help to make the final query more readable. I haven't changed the answer in any way and I'm not looking to have this answer accepted or even up-voted. I just wanted to help add some clarity in how to understand the query.
I've just done a bit of substitution refactoring and have changed the query to be:
HoldPayloads =
EnumeratedSymbolKeys
.Select(o =>
MouseDowns(o)
.Select(_ => Generate().TakeUntil(MouseUps(o)))
.Switch()
.Select(_ => o.Payload))
.Merge();
To me this is much easier to read and the intent of the query is very clear.
It just leaves the definition of MouseDowns, MouseUps, and Generate. These are:
IObservable<Unit> MouseDowns(EnumeratedSymbolKey o) =>
Observable
.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonDown += h, h => o.PreviewMouseLeftButtonDown -= h)
.Select(EncodingProvider => Unit.Default);
IObservable<Unit> MouseUps(EnumeratedSymbolKey o) =>
Observable
.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonUp += h, h => o.PreviewMouseLeftButtonUp -= h)
.Select(EncodingProvider => Unit.Default);
IObservable<int> Generate() =>
Observable
.Generate(1, i => true, i => i + 1, i => i,
i => i == 1
? TimeSpan.FromMilliseconds(0)
: (i > 10 ? TimeSpan.FromMilliseconds(50) : TimeSpan.FromMilliseconds(500 / i)));
Now that these are separated out it's easier to confirm that each are correct and hopefully that the whole code is correct.

Related

Heartbeat pattern using reactive extension

Given a simple scenario:
A and B are in a room, A talks to B. The room is dark and B couldn't see A. How could B figure out if A is pausing or A is kidnapped from the room?
When A talks, A provides IObservable Talk that B subsequently subscribes to Talk.Subscribe(string=>process what A said). B could at the same time subscribe to Observable.Interval Heartbeat as a heartbeat checking.
My question is what Operator I should use to merge/combine two IObservable so that if there is no item from Talk over two items of Heartbeat, B will assume the A has been kidnapped.
Please note that I want to avoid a variable to store the state because it may cause the side effect if I don't synchronize that variable properly.
Thanks,
Imagine a state variable you want to act on, with the state representing the number of heartbeats since 'A' last spoke. That would look like this:
var stateObservable = Observable.Merge( //State represent number of heartbeats since A last spoke
aSource.Select(_ => new Func<int, int>(i => 0)), //When a talks, set state to 0
bHeartbeat.Select(_ => new Func<int, int>(i => i + 1)) //when b heartbeats, increment state
)
.Scan(0, (state, func) => func(state));
We represent incidents of A speaking as a function resetting the state to 0, and incidents of B heartbeatting as incrementing the state. We then accumulate with the Scan function.
The rest is now easy:
var isKidnapped = stateObservable
.Where(state => state >= 2)
.Take(1);
isKidnapped.Subscribe(_ => Console.WriteLine("A is kidnapped"));
EDIT:
Here's an example with n A sources:
var aSources = new Subject<Tuple<string, Subject<string>>>();
var bHeartbeat = Observable.Interval(TimeSpan.FromSeconds(1)).Publish().RefCount();
var stateObservable = aSources.SelectMany(t =>
Observable.Merge(
t.Item2.Select(_ => new Func<int, int>(i => 0)),
bHeartbeat.Select(_ => new Func<int, int>(i => i + 1))
)
.Scan(0, (state, func) => func(state))
.Where(state => state >= 2)
.Take(1)
.Select(_ => t.Item1)
);
stateObservable.Subscribe(s => Console.WriteLine($"{s} is kidnapped"));
aSources
.SelectMany(t => t.Item2.Select(s => Tuple.Create(t.Item1, s)))
.Subscribe(t => Console.WriteLine($"{t.Item1} says '{t.Item2}'"));
bHeartbeat.Subscribe(_ => Console.WriteLine("**Heartbeat**"));
var a = new Subject<string>();
var c = new Subject<string>();
var d = new Subject<string>();
var e = new Subject<string>();
var f = new Subject<string>();
aSources.OnNext(Tuple.Create("A", a));
aSources.OnNext(Tuple.Create("C", c));
aSources.OnNext(Tuple.Create("D", d));
aSources.OnNext(Tuple.Create("E", e));
aSources.OnNext(Tuple.Create("F", f));
a.OnNext("Hello");
c.OnNext("My name is C");
d.OnNext("D is for Dog");
await Task.Delay(TimeSpan.FromMilliseconds(1200));
e.OnNext("Easy-E here");
a.OnNext("A is for Apple");
await Task.Delay(TimeSpan.FromMilliseconds(2200));

How to merge 2 observables without breaking current subscriptions? [duplicate]

We are using Rx to monitor activity within our silverlight application so that we can display a message to the user after a period of inactivity.
We are turning events (mouse moves etc.) into observables and then merging the observables together to create a single (allActivity) observable. We then throttle the allActivity observable using a timespan and something subscribes to be notified when the system has been inactive for a period of time.
How can I add a new observable/ sequence to this after the subscription (so that the subscription picks this up without unsubscribing and resubscribing).
e.g. merge several sequences together, throttle, subscribe. Now add an additional sequence to the observable that has been subscribed to.
Example code:
private IObservable<DateTime> allActivity;
public void CreateActivityObservables(UIElement uiElement)
{
// Create IObservables of event types we are interested in and project them as DateTimes
// These are our observables sequences that can push data to subscribers/ observers
// NB: These are like IQueryables in the sense that they do not iterate over the sequence just provide an IObservable type
var mouseMoveActivity = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(h => uiElement.MouseMove += h, h => uiElement.MouseMove -= h)
.Select(o => DateTime.Now);
var mouseLeftButtonActivity = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(h => uiElement.MouseLeftButtonDown += h, h => uiElement.MouseLeftButtonDown -= h)
.Select(o => DateTime.Now);
var mouseRightButtonActivity = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(h => uiElement.MouseRightButtonDown += h, h => uiElement.MouseRightButtonDown -= h)
.Select(o => DateTime.Now);
var mouseWheelActivity = Observable.FromEventPattern<MouseWheelEventHandler, MouseWheelEventArgs>(h => uiElement.MouseWheel += h, h => uiElement.MouseWheel -= h)
.Select(o => DateTime.Now);
var keyboardActivity = Observable.FromEventPattern<KeyEventHandler, KeyEventArgs>(h => uiElement.KeyDown += h, h => uiElement.KeyDown -= h)
.Select(o => DateTime.Now);
var streetViewContainer = HtmlPage.Document.GetElementById("streetViewContainer");
var mouseMoveHandler = new EventHandler<HtmlEventArgs>(this.Moo);
bool b = streetViewContainer.AttachEvent("mousemove", mouseMoveHandler);
var browserActivity = Observable.FromEventPattern<Landmark.QDesk.ApplicationServices.IdleTimeoutService.MouseMoveHandler, HtmlEventArgs>(h => this.MyMouseMove += h, h => this.MyMouseMove -= h).Select(o => DateTime.Now);
// Merge the IObservables<DateTime> together into one stream/ sequence
this.allActivity = mouseMoveActivity.Merge(mouseLeftButtonActivity)
.Merge(mouseRightButtonActivity)
.Merge(mouseWheelActivity)
.Merge(keyboardActivity)
.Merge(browserActivity);
}
public IDisposable Subscribe(TimeSpan timeSpan, Action<DateTime> timeoutAction)
{
IObservable<DateTime> timeoutNotification = this.allActivity.Merge (IdleTimeoutService.GetDateTimeNowObservable())
.Throttle(timeSpan)
.ObserveOn(Scheduler.ThreadPool);
return timeoutNotification.Subscribe(timeoutAction);
}
There's an overload to Merge that takes in an IObservable<IObservable<TSource>>. Make the outer sequence a Subject<IObservable<TSource>> and call OnNext to it when you want to add another source to the bunch. The Merge operator will receive the source and subscribe to it:
var xss = new Subject<IObservable<int>>();
xss.Merge().Subscribe(x => Console.WriteLine(x));
xss.OnNext(Observable.Interval(TimeSpan.FromSeconds(1.0)).Select(x => 23 + 8 * (int)x));
xss.OnNext(Observable.Interval(TimeSpan.FromSeconds(0.8)).Select(x => 17 + 3 * (int)x));
xss.OnNext(Observable.Interval(TimeSpan.FromSeconds(1.3)).Select(x => 31 + 2 * (int)x));
...
The easiest way to do this would be to use an intermediate subject in place of the Merge calls.
Subject<DateTime> allActivities = new Subject<DateTime>();
var activitySubscriptions = new CompositeDisposable();
activitySubscriptions.Add(mouseMoveActivity.Subscribe(allActivities));
activitySubscriptions.Add(mouseLeftButtonActivity.Subscribe(allActivities));
//etc ...
//subscribe to activities
allActivities.Throttle(timeSpan)
.Subscribe(timeoutAction);
//later add another
activitySubscriptions.Add(newActivity.Subscribe(allActivities));
The Subject class will stop passing OnNext (and further OnError and OnCompleted) events from any of the observables it is subscribed to if it receives any OnError or OnCompleted.
The main difference between this approach and your sample is that it subscribes to all the events when the subject is created, rather than when you subscribe to the merged observable. Since all of the observables in your example are hot, the difference should not be noticeable.

How do I create an Rx observable that gets an immediate value and then samples?

I want to use Sample to reduce the frequency of items coming out of my observable, but I want to immediately see the first event go through without being held up for the sample duration. After that I want the Sample to only give me an item on the sample interval.
The code I have for the simple Sample is:
var sampler = Observable
.Interval(TimeSpan.FromSeconds(2))
.Select(_ => Unit.Default);
var seq = Observable.FromEventPattern<IntEventArgs>(h => _eventSource.Happened += h, h => _eventSource.Happened -= h)
.Sample(sampler);
So I tried to use this to make it produce an item immediately, however that stops the observable working altogether:
var seq = Observable.FromEventPattern<IntEventArgs>(h => _eventSource.Happened += h, h => _eventSource.Happened -= h)
.Sample(Observable.Return(Unit.Default).Concat(sampler));
Then I thought maybe the problem is the Unit.Default part of the sampler so I tried getting rid of that but now that gives a compiler error:
var sampler = Observable
.Interval(TimeSpan.FromSeconds(2));
var seq = Observable.FromEventPattern<IntEventArgs>(h => _eventSource.Happened += h, h => _eventSource.Happened -= h)
.Observable.Return(Unit.Default).Concat(sampler);
I've tried googling for things like "c# immediate observable sample" but nothing shows up, I guess I'm using the wrong terminology but not sure what I do need...
Any ideas please?
Does this work for you?
var observable = Observable.Merge<IntEventArgs>(h => _eventSource.Happened += h,
h => _eventSource.Happened -= h)
.Publish()
.RefCount();
var seq = Observable.Merge<IntEventArgs>(observable.FirstAsync(),
observable.Skip(1).Sample(sampler));
The Publish() method makes sure that you register only once to your event.

Throttle also delays first sample

I'm trying to tame a keyboard triggered event that without throttling would fire with a very high frequency. The code below works but it also delays the first invocation of GlobalCopy by the throttling timespan. Is there a way to get the first sample without delay?
observableGlobalCopy = Observable
.FromEventPattern<EventHandler, EventArgs>(h => this.GlobalCopy += h, h => this.GlobalCopy -= h);
observableGlobalCopy
.SubscribeOnDispatcher()
.Throttle(GlobalEventThottle)
.Subscribe(x => GlobalCopyHandler());
You could try something like this:
var observableGlobalCopy2 = observableGlobalCopy
.Window(() => Observable.Timer(TimeSpan.FromSeconds(1.0)))
.Select(_ => _.Take(1))
.Merge();
If I understand your requirements correctly, how about this?
observableGlobalCopy.Take(1)
.Concat(
observableGlobalCopy.Throttle(GlobalEventThottle))
.SubscribeOnDispatcher()
.Subscribe(x => GlobalCopyHandler());

Time-varying values in Rx

I have two observables. One is from Observable.fromEvent(..), where the underlying event is the user checking a Winforms checkbox. The other is Observable.Interval(..) which I subscribe to in order to do some IO, and I would like to prevent this observable from doing IO, whenever the checkbox is not checked.
I could do it like this:
var gui = new GUI();
var booleans = Observable
.FromEvent<GUI.NewAllowHandler, bool>(
h => gui.NewAllow += h,
h => gui.NewAllow -= h)
Observable.Interval(TimeSpan.FromSeconds(10))
.CombineLatest(booleans, Tuple.Create)
.Where(t => t.Item2)
.Select(t => t.Item1)
.Subscribe(l => DoStuff(l));
but this has the overhead of mixing the booleans in and out of the stream. A nicer way of doing this would be, if I could generate a time-varying value from the booleans variable, which at all times had the value of the last event. Then I could do something like this:
var gui = new GUI();
var booleanState = Observable // typeof(booleanState) == ???
.FromEvent<GUI.NewAllowHandler, bool>(
h => gui.NewAllow += h,
h => gui.NewAllow -= h)
.TimeValue() // hypothetical syntax
Observable.Interval(TimeSpan.FromSeconds(10))
.Where(_ => booleanState)
.Subscribe(l => DoStuff(l));
, which to me seems much closer to the problem statement. Is there anything like this in Rx, or is there anything else, that could make such problems easier to handle?
The Where statement in your interval should work with a properly scoped normal bool:
var booleans = Observable
.FromEvent<GUI.NewAllowHandler, bool>(
h => gui.NewAllow += h,
h => gui.NewAllow -= h)
var isBoxChecked = false;
booleans.Subscribe(t => isBoxChecked = t);
Observable.Interval(TimeSpan.FromSeconds(10))
.Where(_ => isBoxChecked)
.Subscribe(l => DoStuff(l))
Edit: Per your comment, another way of doing it:
intervals = Observable.Interval(TimeSpan.FromSeconds(10));
booleans
.Where(t => t)
.SelectMany(_ => intervals.TakeUntil(booleans))
.Subscribe(l => DoStuff(l))
You need to model the checkbox checked state as Behavior and not as Event stream (because behavior has always a value and this value changes over a period of time - which fits with checkbox checked state). So you can do something like:
var booleans = new BehaviorSubject<bool>(chk.Checked)
var chkEvents = ... //generate boolean observable from checkbox check event
chkEvents.Subscribe(booleans);
Observable.Interval(TimeSpan.FromSeconds(10))
.Where(i => booleans.First())
.Subscribe(i => DoIO());
I'm going to give you two solutions. The first is a very simple and hopefully obvious one using only one observable. The second is a uses both observables.
Since you want to allow the IO only when the box is checked then this is the simplest approach:
Observable
.Interval(TimeSpan.FromSeconds(10))
.Where(_ => gui.IsChecked)
.Subscribe(l => DoStuff(l));
No need at all for the other observable.
But if you really need to use it then the Switch() extension method is your best bet. Try this:
booleans
.Select(b => b == true
? Observable.Interval(TimeSpan.FromSeconds(10))
: Observable.Empty<long>())
.Switch()
.Subscribe(l => DoStuff(l));
It's pretty clean and helps to show that there are empty periods if the checkbox is not ticked.
I hope this helps.

Categories