I want to create an extension method of the form:
IObservable<bool> CancellableTimer( this IObservable source, TimeSpan delay )
{
...
}
which produces a sequence which is always false when the source is, but will go true when the source sequence has stayed true for a period defined by a delay, t:
source: 0---1---------0--1-0-1-0-1-0-1----------0
t------> t------>
result: 0----------1--0---------------------1---0
I'm sure there must be a way to do this using Rx primitives but I'm new to Rx and having trouble getting my head round it. Any ideas please?
Okay so this is what I came up with. I also renamed the method to AsymetricDelay() as it seems like a more appropriate name:
static public IObservable<bool> AsymetricDelay(this IObservable<bool> source, TimeSpan delay, IScheduler scheduler)
{
var distinct = source.DistinctUntilChanged();
return distinct.
Throttle(delay, scheduler) // Delay both trues and falses
.Where(x => x) // But we only want trues to be delayed
.Merge( // Merge the trues with...
distinct.Where(x=>!x) // non delayed falses
)
.DistinctUntilChanged(); // Get rid of any repeated values
}
And here is a unit test to confirm its operation:
[Fact]
public static void Test_AsymetricDelay()
{
var scheduler = new TestScheduler();
var xs = scheduler.CreateHotObservable(
new Recorded<Notification<bool>>(10000000, Notification.CreateOnNext(true)),
new Recorded<Notification<bool>>(60000000, Notification.CreateOnNext(false)),
new Recorded<Notification<bool>>(70000000, Notification.CreateOnNext(true)),
new Recorded<Notification<bool>>(80000000, Notification.CreateOnNext(false)),
new Recorded<Notification<bool>>(100000000, Notification.CreateOnCompleted<bool>())
);
var dest = xs.DelayOn( TimeSpan.FromSeconds(2), scheduler);
var testObserver = scheduler.Start(
() => dest,
0,
0,
TimeSpan.FromSeconds(10).Ticks);
testObserver.Messages.AssertEqual(
new Recorded<Notification<bool>>(30000000, Notification.CreateOnNext(true)),
new Recorded<Notification<bool>>(60000000, Notification.CreateOnNext(false)),
new Recorded<Notification<bool>>(100000000, Notification.CreateOnCompleted<bool>())
);
}
Related
I want to observe state change in Internet connection from my Xamarin App. I use the Connectivity Plugin which offers an ConnectivityChanged event which I convert into an Observable. This works fine.
The problem is when the Phones switches e.g. from 3G to 4G there is a short disconnection which I would like to ignore.
So far I tried with Throttle and Distinctuntil changed but with no luck as I always get a value even if the state is the same as before the disconnect.
So far I tried this:
var connectionEvent = Observable.FromEventPattern<ConnectivityChangedEventHandler, ConnectivityChangedEventArgs>(
handler => handler.Invoke,
h => CrossConnectivity.Current.ConnectivityChanged += h,
h => CrossConnectivity.Current.ConnectivityChanged -= h)
.Select(x => x.EventArgs.IsConnected)
.Throttle(TimeSpan.FromSeconds(5))
.DistinctUntilChanged()
.StartWith(CrossConnectivity.Current.IsConnected)
.Publish();
It's rather hard to figure out what's happening with "Stuff's firing when I don't want it to". Luckily, Rx has some robust testing support. Here's some test code ( using Nuget Install-Package Microsoft.Reactive.Testing). source represents noise coming from the Android API. target is your set of operators on top of it. expectedResults is the results:
TestScheduler ts = new TestScheduler();
var crossConnectivity_Current_IsConnected = true;
var source = ts.CreateHotObservable<bool>(
new Recorded<Notification<bool>>(200.MsTicks(), Notification.CreateOnNext(false)),
new Recorded<Notification<bool>>(300.MsTicks(), Notification.CreateOnNext(true)),
new Recorded<Notification<bool>>(5550.MsTicks(), Notification.CreateOnNext(false)),
new Recorded<Notification<bool>>(5600.MsTicks(), Notification.CreateOnNext(true)),
new Recorded<Notification<bool>>(5800.MsTicks(), Notification.CreateOnNext(false)),
new Recorded<Notification<bool>>(7700.MsTicks(), Notification.CreateOnNext(true)),
new Recorded<Notification<bool>>(7800.MsTicks(), Notification.CreateOnNext(false)),
new Recorded<Notification<bool>>(13000.MsTicks(), Notification.CreateOnNext(true))
);
var target = source
.Throttle(TimeSpan.FromSeconds(5), ts)
.DistinctUntilChanged()
.StartWith(crossConnectivity_Current_IsConnected);
var expectedResults = ts.CreateHotObservable<bool>(
new Recorded<Notification<bool>>(0.MsTicks(), Notification.CreateOnNext(true)),
new Recorded<Notification<bool>>(5300.MsTicks(), Notification.CreateOnNext(true)),
new Recorded<Notification<bool>>(12800.MsTicks(), Notification.CreateOnNext(false)),
new Recorded<Notification<bool>>(18000.MsTicks(), Notification.CreateOnNext(true))
);
The only problem I'm seeing is the notification at 5300. You can get rid of that by re-ordering the operators a bit. Look at my target2:
var target2 = source
.Throttle(TimeSpan.FromSeconds(5), ts)
.StartWith(crossConnectivity_Current_IsConnected)
.DistinctUntilChanged()
;
var expectedResults2 = ts.CreateHotObservable<bool>(
new Recorded<Notification<bool>>(0.MsTicks(), Notification.CreateOnNext(true)),
new Recorded<Notification<bool>>(12800.MsTicks(), Notification.CreateOnNext(false)),
new Recorded<Notification<bool>>(18000.MsTicks(), Notification.CreateOnNext(true))
);
...and if you want to run the tests, here's the runner code.
var observer = ts.CreateObserver<bool>();
target.Subscribe(observer);
var observer2 = ts.CreateObserver<bool>();
target2.Subscribe(observer2);
ts.Start();
ReactiveAssert.AreElementsEqual(expectedResults.Messages, observer.Messages);
ReactiveAssert.AreElementsEqual(expectedResults2.Messages, observer2.Messages);
...and you'll need this class
public static class Extensions
{
public static long MsTicks(this int i)
{
return TimeSpan.FromMilliseconds(i).Ticks;
}
}
If that doesn't solve your problem, augment source to describe when/how it occurs.
Here we have a Observable Sequence... in .NET using Rx.
var aSource = new Subject<int>();
var bSource = new Subject<int>();
var paired = Observable
.Merge(aSource, bSource)
.GroupBy(i => i).SelectMany(g => g.Buffer(2).Take(1));
paired.Subscribe(g => Console.WriteLine("{0}:{1}", g.ElementAt(0), g.ElementAt(1)));
aSource.OnNext(4);
bSource.OnNext(1);
aSource.OnNext(2);
bSource.OnNext(5);
aSource.OnNext(3);
bSource.OnNext(3);
aSource.OnNext(5);
bSource.OnNext(2);
aSource.OnNext(1);
bSource.OnNext(4);
Output:
3:3
5:5
2:2
1:1
4:4
We will get events every time a pair of numbers arrive with the same id.
Perfect! Just what i want.
Groups of two, paired by value.
Next question....
How to get a selectmany/buffer for sequences of values.
So 1,2,3,4,5 arrives at both aSource and bSource via OnNext(). Then fire ConsoleWriteLine() for 1-5. Then when 2,3,4,5,6 arrives, we get another console.writeline(). Any clues anyone?
Immediately, the Rx forum suggests looking at .Window()
http://introtorx.com/Content/v1.0.10621.0/17_SequencesOfCoincidence.html
Which on the surface looks perfect. In my case i need a window of value 4, in this case.
Where in the query sequence does it belong to get this effect?
var paired = Observable.Merge(aSource, bSource).GroupBy(i => i).SelectMany(g => g.Buffer(2).Take(1));
Output
1,2,3,4,5 : 1,2,3,4,5
2,3,4,5,6 : 2,3,4,5,6
Regards,
Daniel
Assuming events arrive randomly at the sources, use my answer to "Reordering events with Reactive Extensions" to get the events in order.
Then use Observable.Buffer to create a sliding buffer:
// get this using the OrderedCollect/Sort in the referenced question
IObservable<int> orderedSource;
// then subscribe to this
orderedSource.Buffer(5, 1);
Here is an extension method that fires when it has n inputs of the same ids.
public static class RxExtension
{
public static IObservable<TSource> MergeBuffer<TSource>(this IObservable<TSource> source, Func<TSource, int> keySelector, Func<IList<TSource>,TSource> mergeFunction, int bufferCount)
{
return Observable.Create<TSource>(o => {
var buffer = new Dictionary<int, IList<TSource>>();
return source.Subscribe<TSource>(i =>
{
var index = keySelector(i);
if (buffer.ContainsKey(index))
{
buffer[index].Add(i);
}
else
{
buffer.Add(index, new List<TSource>(){i});
}
if (buffer.Count==bufferCount)
{
o.OnNext(mergeFunction(buffer[index]));
buffer.Remove(index);
}
});
});
}
}
Calling the extension.
mainInput = Observable.Merge(inputNodes.ToArray()).MergeBuffer<NodeData>(x => x.id, x => MergeData(x), 1);
I currently have a program that listens to a network stream and fires events when a new message has been deserialized.
while(true)
{
byte[] lengthBytes = new byte[10];
networkStream.Read(lengthBytes, 0, 10);
int messageLength = Int32.Parse(Encoding.UTF8.GetString(lengthBytes));
var messageBytes = new byte[messageLength + 10];
Array.Copy(lengthBytes, messageBytes, 10);
int bytesReadTotal = 10;
while (bytesReadTotal < 10 + messageLength)
bytesReadTotal += networkStream.Read(messageBytes, bytesReadTotal, messageLength - bytesReadTotal + 10);
OnNewMessage(new MessageEventArgs(messageFactory.GetMessage(messageBytes)));
}
I want to rewrite this using the reactive extensions so that instead of the event there is an IObservable<Message>. This could be done using
Observable.FromEvent<EventHandler<MessageEventArgs>, MessageEventArgs>(
(h) => NewMessage += h,
(h) => NewMessage -= h)
.Select( (e) => { return e.Message; });
However I would prefer to rewrite the listening process using System.Reactive instead. My starting point (from here) is
Func<byte[], int, int, IObservable<int>> read;
read = Observable.FromAsyncPattern<byte[], int, int, int>(
networkStream.BeginRead,
networkStream.EndRead);
which allows
byte[] lengthBytes = new byte[10];
read(lengthBytes, 0, lengthBytes.Length).Subscribe(
{
(bytesRead) => ;
});
I'm struggling to see how to continue though. Does anyone have an implementation?
I came up with the following, but I feel it should be possible without creating a class and using Subject<T> (e.g. via some projection of the header packet to the body packet to the message object, but the problem with that is EndRead() doesn't return the byte array, but the number of bytes read. So you need an object or atleast a closure at some point).
class Message
{
public string Text { get; set; }
}
class MessageStream : IObservable<Message>
{
private readonly Subject<Message> messages = new Subject<Message>();
public void Start()
{
// Get your real network stream here.
var stream = Console.OpenStandardInput();
GetNextMessage( stream );
}
private void GetNextMessage(Stream stream)
{
var header = new byte[10];
var read = Observable.FromAsyncPattern<byte [], int, int, int>( stream.BeginRead, stream.EndRead );
read( header, 0, 10 ).Subscribe( b =>
{
var bodyLength = BitConverter.ToInt32( header, 0 );
var body = new byte[bodyLength];
read( body, 0, bodyLength ).Subscribe( b2 =>
{
var message = new Message() {Text = Encoding.UTF8.GetString( body )};
messages.OnNext( message );
GetNextMessage( stream );
} );
} );
}
public IDisposable Subscribe( IObserver<Message> observer )
{
return messages.Subscribe( observer );
}
}
Since Observable.FromAsyncPattern only makes the async call once, you will need to make a function that will call it multiple times instead. This should get you started, but probably has lots of room for improvement. It assumes that you can make the async calls repeatedly with the same arguments and assumes that the selector will handle any issues that arise from this.
Function FromRepeatedAsyncPattern(Of T1, T2, T3, TCallResult, TResult)(
begin As Func(Of T1, T2, T3, AsyncCallback, Object, IAsyncResult),
[end] As Func(Of IAsyncResult, TCallResult),
selector As Func(Of TCallResult, TResult),
isComplete As Func(Of TCallResult, Boolean)
) As Func(Of T1, T2, T3, IObservable(Of TResult))
Return Function(a1, a2, a3) Observable.Create(Of TResult)(
Function(obs)
Dim serial As New SerialDisposable()
Dim fac = Observable.FromAsyncPattern(begin, [end])
Dim onNext As Action(Of TCallResult) = Nothing
'this function will restart the subscription and will be
'called every time a value is found
Dim subscribe As Func(Of IDisposable) =
Function()
'note that we are REUSING the arguments, the
'selector should handle this appropriately
Return fac(a1, a2, a3).Subscribe(onNext,
Sub(ex)
obs.OnError(ex)
serial.Dispose()
End Sub)
End Function
'set up the OnNext handler to restart the observer
'every time it completes
onNext = Sub(v)
obs.OnNext(selector(v))
'subscriber disposed, do not check for completion
'or resubscribe
If serial.IsDisposed Then Exit Sub
If isComplete(v) Then
obs.OnCompleted()
serial.Dispose()
Else
'using the scheduler lets the OnNext complete before
'making the next async call.
'you could parameterize the scheduler, but it may not be
'helpful, and it won't work if Immediate is passed.
Scheduler.CurrentThread.Schedule(Sub() serial.Disposable = subscribe())
End If
End Sub
'start the first subscription
serial.Disposable = subscribe()
Return serial
End Function)
End Function
From here, you can get an IObservable(Of Byte) like so:
Dim buffer(4096 - 1) As Byte
Dim obsFac = FromRepeatedAsyncPattern(Of Byte(), Integer, Integer, Integer, Byte())(
AddressOf stream.BeginRead, AddressOf stream.EndRead,
Function(numRead)
If numRead < 0 Then Throw New ArgumentException("Invalid number read")
Console.WriteLine("Position after read: " & stream.Position.ToString())
Dim ret(numRead - 1) As Byte
Array.Copy(buffer, ret, numRead)
Return ret
End Function,
Function(numRead) numRead <= 0)
'this will be an observable of the chunk size you specify
Dim obs = obsFac(buffer, 0, buffer.Length)
From there, you will need some sort of accumulator function that takes byte arrays and outputs complete messages when they are found. The skeleton of such a function might look like:
Public Function Accumulate(source As IObservable(Of Byte())) As IObservable(Of Message)
Return Observable.Create(Of message)(
Function(obs)
Dim accumulator As New List(Of Byte)
Return source.Subscribe(
Sub(buffer)
'do some logic to build a packet here
accumulator.AddRange(buffer)
If True Then
obs.OnNext(New message())
'reset accumulator
End If
End Sub,
AddressOf obs.OnError,
AddressOf obs.OnCompleted)
End Function)
End Function
I want to stop stream A for exactly one notification whenever stream B fires. Both streams will stay online and won't ever complete.
A: o--o--o--o--o--o--o--o--o
B: --o-----o--------o-------
R: o-----o-----o--o-----o--o
or
A: o--o--o--o--o--o--o--o--o
B: -oo----oo-------oo-------
R: o-----o-----o--o-----o--o
Here's a version of my SkipWhen operator I did for a similar question (the difference is that, in the original, multiple "B's" would skip multiple "A's"):
public static IObservable<TSource> SkipWhen<TSource, TOther>(this IObservable<TSource> source,
IObservable<TOther> other)
{
return Observable.Create<TSource>(observer =>
{
object lockObject = new object();
bool shouldSkip = false;
var otherSubscription = new MutableDisposable();
var sourceSubscription = new MutableDisposable();
otherSubscription.Disposable = other.Subscribe(
x => { lock(lockObject) { shouldSkip = true; } });
sourceSubscription.Disposable = source.Where(_ =>
{
lock(lockObject)
{
if (shouldSkip)
{
shouldSkip = false;
return false;
}
else
{
return true;
}
}
}).Subscribe(observer);
return new CompositeDisposable(
sourceSubscription, otherSubscription);
});
}
If the current implementation becomes a bottleneck, consider changing the lock implementation to use a ReaderWriterLockSlim.
This solution will work when the observable is hot (and without refCount):
streamA
.takeUntil(streamB)
.skip(1)
.repeat()
.merge(streamA.take(1))
.subscribe(console.log);
.takeUntil(streamB): make stream A complete upon stream B producing a value.
.skip(1): make stream A skip one value upon starting (or as a result of .repeat()).
.repeat(): make stream A repeat (reconnect) indefinitely.
.merge(streamA.take(1)): offset the effect of .skip(1) at the beginning of the stream.
Example of making A stream skip every 5 seconds:
var streamA,
streamB;
streamA = Rx.Observable
.interval(1000)
.map(function (x) {
return 'A:' + x;
}).publish();
streamB = Rx.Observable
.interval(5000);
streamA
.takeUntil(streamB)
.skip(1)
.repeat()
.merge(streamA.take(1))
.subscribe(console.log);
streamA.connect();
You can also use this sandbox http://jsbin.com/gijorid/4/edit?js,console to execute BACTION() in the console log at the time of running the code to manually push a value to streamB (which is helpful for analysing the code).
I know that there is an easy way to do this - but it has beaten me tonight ...
I want to know if two events occur within 300 milliseconds of each other, as in a double click.
Two leftdown mouse clicks in 300 milliseconds - I know this is what the reactive framework was built for - but damn if I can find a good doc that has simple examples for all the extenstion operatores - Throttle, BufferWithCount, BufferWithTime - all of which just werent' doing it for me....
The TimeInterval method will give you the time between values.
public static IObservable<Unit> DoubleClicks<TSource>(
this IObservable<TSource> source, TimeSpan doubleClickSpeed, IScheduler scheduler)
{
return source
.TimeInterval(scheduler)
.Skip(1)
.Where(interval => interval.Interval <= doubleClickSpeed)
.RemoveTimeInterval();
}
If you want to be sure that triple clicks don't trigger values, you could just use Repeat on a hot observable (I've used a FastSubject here as the clicks will all come on one thread and therefore don't require the heaviness of the normal Subjects):
public static IObservable<TSource> DoubleClicks<TSource>(
this IObservable<TSource> source, TimeSpan doubleClickSpeed, IScheduler scheduler)
{
return source.Multicast<TSource, TSource, TSource>(
() => new FastSubject<TSource>(), // events won't be multithreaded
values =>
{
return values
.TimeInterval(scheduler)
.Skip(1)
.Where(interval => interval.Interval <= doubleClickSpeed)
.RemoveTimeInterval()
.Take(1)
.Repeat();
});
}
Edit - Use TimeInterval() instead.
The Zip() and Timestamp() operators might be a good start.
var ioClicks = Observable.FromEvent<MouseButtonEventHandler, RoutedEventArgs>(
h => new MouseButtonEventHandler(h),
h => btn.MouseLeftButtonDown += h,
h => btn.MouseLeftButtonDown -= h);
var ioTSClicks = ioClicks.Timestamp();
var iodblClicks = ioTSClicks.Zip(ioTSClicks.Skip(1),
(r, l) => l.Timestamp - r.Timestamp)
.Where(tspan => tspan.TotalMilliseconds < 300);
Probably best to test this via the test scheduler, so you know exactly what you're getting:
[Fact]
public void DblClick()
{
// setup
var ioClicks = _scheduler.CreateHotObservable(
OnNext(210, "click"),
OnNext(220, "click"),
OnNext(300, "click"),
OnNext(365, "click"))
.Timestamp(_scheduler);
// act
Func<IObservable<TimeSpan>> target =
() => ioClicks.Zip(ioClicks.Skip(1),
(r, l) => l.Timestamp - r.Timestamp)
.Where(tspan => tspan.Ticks < 30);
var actuals = _scheduler.Run(target);
// assert
Assert.Equal(actuals.Count(), 1);
// + more
}
public static Recorded<Notification<T>> OnNext<T>(long ticks, T value)
{
return new Recorded<Notification<T>>(
ticks,
new Notification<T>.OnNext(value));
}