Reputation: 6665
I'm trying to figure out how to create a Reactive subscription for an event not followed by a different event within a specific time window.
To illustrate, here's a use case:
A busy indicator triggered by two events. Busy and NotBusy. They may fire close together but the indicator should not flash on/off too often.
When NotBusy fires, there should be no indicator. When Busy fires and NotBusy hasn't fired within 5 seconds then it should display.
Is there a way to do this entirely within Reactive without adding external state?
Upvotes: 3
Views: 355
Reputation: 16934
Ah, I can't resist - how often do you get to counter-answer the author of the source material? (see Benjol's comment on question) :)
Here's my stab at it (LINQPad-ready):
Output looks like this:
At 1/1/0001 12:00:00 AM +00:00, Busy Signal Indicator is now:OFF
Sending BUSY at 1/1/0001 12:00:00 AM +00:00
Sending NOTBUSY at 1/1/0001 12:00:02 AM +00:00
Sending BUSY at 1/1/0001 12:00:03 AM +00:00
Sending BUSY at 1/1/0001 12:00:06 AM +00:00
At 1/1/0001 12:00:08 AM +00:00, Busy Signal Indicator is now:ON
Sending NOTBUSY at 1/1/0001 12:00:09 AM +00:00
At 1/1/0001 12:00:09 AM +00:00, Busy Signal Indicator is now:OFF
Here a basic definition of our events:
enum EventType
{
Busy,
NotBusy
}
class StreamEvent
{
public EventType Type {get; set;}
public StreamEvent(EventType type) { Type = type;}
}
And here's the query + test code:
void Main()
{
// our simulated event stream
var fakeSource = new System.Reactive.Subjects.Subject<StreamEvent>();
// Let's use a scheduler we actually don't have to wait for
var theTardis = new System.Reactive.Concurrency.HistoricalScheduler();
var busySignal = fakeSource
// Batch up events:
.Window(
// Starting batching on a busy signal
fakeSource.Where(e => e.Type == EventType.Busy),
// Stop batching on a not busy signal
(open) => fakeSource.Where(e => e.Type == EventType.NotBusy)
// but throw a timeout if we exceed 5 seconds per window
.Timeout(TimeSpan.FromSeconds(5), theTardis))
// Unpack the windows
.Switch()
// Catch any timeout exception and inject a NULL into the stream
.Catch(fakeSource.StartWith((StreamEvent)null))
// Bool-ify on "did a timeout happen?"
.Select(e => e == null)
// Start in an "unbusy" state
.StartWith(false)
// And only tell us about transitions
.DistinctUntilChanged();
using(busySignal.Subscribe(signal =>
Console.WriteLine("At {0}, Busy Signal Indicator is now:{1}",
theTardis.Now,
signal ? "ON" : "OFF")))
{
// should not generate a busy signal
Console.WriteLine("Sending BUSY at {0}", theTardis.Now);
fakeSource.OnNext(new StreamEvent(EventType.Busy));
theTardis.AdvanceBy(TimeSpan.FromSeconds(2));
Console.WriteLine("Sending NOTBUSY at {0}", theTardis.Now);
fakeSource.OnNext(new StreamEvent(EventType.NotBusy));
theTardis.AdvanceBy(TimeSpan.FromSeconds(1));
Console.WriteLine();
// should generate a busy signal
Console.WriteLine("Sending BUSY at {0}", theTardis.Now);
fakeSource.OnNext(new StreamEvent(EventType.Busy));
theTardis.AdvanceBy(TimeSpan.FromSeconds(3));
Console.WriteLine("Sending BUSY at {0}", theTardis.Now);
fakeSource.OnNext(new StreamEvent(EventType.Busy));
theTardis.AdvanceBy(TimeSpan.FromSeconds(3));
// and this should clear it
Console.WriteLine("Sending NOTBUSY at {0}", theTardis.Now);
fakeSource.OnNext(new StreamEvent(EventType.NotBusy));
theTardis.AdvanceBy(TimeSpan.FromSeconds(1));
Console.WriteLine();
}
}
Upvotes: 1