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.
Related
I am trying to create an IObservable<string> from the following code, but i can't seem to find a way of how to properly unwrap the value of the event handler.
What is happening is that the PasswordBox might change, so whenever it does i want to observe on that, and provide a string resource whenever the password changed event is raised. It works fine if i do it with ordinary events but i am curious on how this would work using System.Reactive.
var passwordChanged = WhenPropertyChanged
.Where(name => nameof(PasswordBox) == name)
.Select(d => PasswordBox)
.Where(d => d != null)
.Select(box =>
{
return Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
handler => box.PasswordChanged += handler,
handler => box.PasswordChanged -= handler);
}).Select(d => nameof(Password));
to me it seems like there has to be some way within the Select(box => ... part where i can return a different object (other than IObservable<IObservable<RoutedEventArgs>>), which can be used to subscribe to properly.
Doing it like the following works. But i think reactive does a better job of avoiding event handler memory leaks if you use it end to end.
var passwordHasChanged = new Subject<string>();
// listen for changes of the password
WhenPropertyChanged
.Where(name => nameof(PasswordBox) == name)
.Select(d => PasswordBox)
.Where(d => d != null)
.Subscribe(box =>
{
box.PasswordChanged += (sender, args) => passwordHasChanged.OnNext(nameof(Password));
});
passwordHasChanged.Subscribe(d => Log.Debug("Password changed"));
Avoid using Subjects wherever possible. Subjects are like the mutable variables of Rx, they don't compose and read imperatively rather than declaratively.
If you want events from only the last password input, use Switch.
Switch works on an IObservable<IObservable<T>>, and unsubscribes from the previous observable when it gets a newer observable.
var passwordChanged = WhenPropertyChanged
.Where(name => nameof(PasswordBox) == name)
.Select(d => PasswordBox)
.Where(d => d != null)
.Select(box =>
Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
handler => box.PasswordChanged += handler,
handler => box.PasswordChanged -= handler);
)
.Switch()
.Select(d => nameof(Password));
I have an observable that emits unique values e.g.
var source=Observable.Range(1,100).Publish();
source.Connect();
I want to observe its values from e.g. two observers but each observer to get notified only for values not seen in other observers.
So if first observer contains the value 10 the second observer should never get notified for the 10 value.
Update
I chose #Asti`s answer cause it was first and although buggy it pointed to the right direction and up-voted #Shlomo's answer. Too bad I cannot accept both answers as #Shlomo answer was more correct and I really appreciate all his help we get on this tag.
Observables aren't supposed to behave differently for different observers; a better approach would be to give each observer its own filtered observable.
That being said, if your constraints require that you need this behavior in a single observable - we can use a Round-Robin method.
public static IEnumerable<T> Repeat<T>(this IEnumerable<T> source)
{
for (; ; )
foreach (var item in source.ToArray())
yield return item;
}
public static IObservable<T> RoundRobin<T>(this IObservable<T> source)
{
var subscribers = new List<IObserver<T>>();
var shared = source
.Zip(subscribers.Repeat(), (value, observer) => (value, observer))
.Publish()
.RefCount();
return Observable.Create<T>(observer =>
{
subscribers.Add(observer);
var subscription =
shared
.Where(pair => pair.observer == observer)
.Select(pair => pair.value)
.Subscribe(observer);
var dispose = Disposable.Create(() => subscribers.Remove(observer));
return new CompositeDisposable(subscription, dispose);
});
}
Usage:
var source = Observable.Range(1, 100).Publish();
var dist = source.RoundRobin();
dist.Subscribe(i => Console.WriteLine($"One sees {i}"));
dist.Subscribe(i => Console.WriteLine($"Two sees {i}"));
source.Connect();
Result:
One sees 1
Two sees 2
One sees 3
Two sees 4
One sees 5
Two sees 6
One sees 7
Two sees 8
One sees 9
Two sees 10
If you already have a list of observers, the code becomes much simpler.
EDIT: #Asti fixed his bug, and I fixed mine based on his answer. Our answers are now largely similar. I have an idea how to do a purely reactive one, if I have time I'll post that later.
Fixed code:
public static IObservable<T> RoundRobin2<T>(this IObservable<T> source)
{
var subscribers = new BehaviorSubject<ImmutableList<IObserver<T>>>(ImmutableList<IObserver<T>>.Empty);
ImmutableList<IObserver<T>> latest = ImmutableList<IObserver<T>>.Empty;
subscribers.Subscribe(l => latest = l);
var shared = source
.Select((v, i) => (v, i))
.WithLatestFrom(subscribers, (t, s) => (t.v, t.i, s))
.Publish()
.RefCount();
return Observable.Create<T>(observer =>
{
subscribers.OnNext(latest.Add(observer));
var dispose = Disposable.Create(() => subscribers.OnNext(latest.Remove(observer)));
var sub = shared
.Where(t => t.i % t.s.Count == t.s.FindIndex(o => o == observer))
.Select(t => t.v)
.Subscribe(observer);
return new CompositeDisposable(dispose, sub);
});
}
Original answer:
I upvoted #Asti's answer, because he's largely correct: Just because you can, doesn't mean you should. And his answer largely works, but it's subject to a bug:
This works fine:
var source = Observable.Range(1, 20).Publish();
var dist = source.RoundRobin();
dist.Subscribe(i => Console.WriteLine($"One sees {i}"));
dist.Take(1).Subscribe(i => Console.WriteLine($"Two sees {i}"));
This doesn't:
var source = Observable.Range(1, 20).Publish();
var dist = source.RoundRobin();
dist.Take(1).Subscribe(i => Console.WriteLine($"One sees {i}"));
dist.Subscribe(i => Console.WriteLine($"Two sees {i}"));
Output is:
One sees 1
Two sees 1
Two sees 2
Two sees 3
Two sees 4
...
I first thought the bug is Halloween related, but now I'm not sure. The .ToArray() in Repeat should take care of that. I also wrote a pure-ish observable implementation which has the same bug. This implementation doesn't guarantee a perfect Round Robin, but that wasn't in the question:
public static IObservable<T> RoundRobin2<T>(this IObservable<T> source)
{
var subscribers = new BehaviorSubject<ImmutableList<IObserver<T>>>(ImmutableList<IObserver<T>>.Empty);
ImmutableList<IObserver<T>> latest = ImmutableList<IObserver<T>>.Empty;
subscribers.Subscribe(l => latest = l);
var shared = source
.Select((v, i) => (v, i))
.WithLatestFrom(subscribers, (t, s) => (t.v, t.i, s))
.Publish()
.RefCount();
return Observable.Create<T>(observer =>
{
subscribers.OnNext(latest.Add(observer));
var dispose = Disposable.Create(() => subscribers.OnNext(latest.Remove(observer)));
var sub = shared
.Where(t => t.i % t.s.Count == t.s.FindIndex(o => o == observer))
.Select(t => t.v)
.Subscribe(observer);
return new CompositeDisposable(dispose, sub);
});
}
This is a simple distributed queue implementation using TPL Dataflow. But with respect to different observers not seeing the same value, there's little chance of it behaving incorrectly. It's not round-robin, but actually has back-pressure semantics.
public static IObservable<T> Distribute<T>(this IObservable<T> source)
{
var buffer = new BufferBlock<T>();
source.Subscribe(buffer.AsObserver());
return Observable.Create<T>(observer =>
buffer.LinkTo(new ActionBlock<T>(observer.OnNext, new ExecutionDataflowBlockOptions { BoundedCapacity = 1 })
);
}
Output
One sees 1
Two sees 2
One sees 3
Two sees 4
One sees 5
One sees 6
One sees 7
One sees 8
One sees 9
One sees 10
I might prefer skipping Rx entirely and just using TPL Dataflow.
I'm trying to use Rx in my Kafka consumer.
public static event EventHandler<ConsumeResult<string, string>> GenericEvent;
then I have the following code
var observable = Observable.FromEventPattern<ConsumeResult<string, string>>(
x => GenericEvent += x,
x => GenericEvent -= x)
.Select(x => x.EventArgs);
while (!cancellationToken.IsCancellationRequested)
{
ConsumeResult<string, string> consumeResult = _consumer.Consume(cancellationToken);
GenericEvent(consumeResult.Topic, consumeResult);
}
then somewhere I use it like
var subscription = observable.Subscribe(message =>
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} ** {message.Topic}/{message.Partition} #{message.Offset}: '{message.Value}'");
//_kafkaConsumer.Commit(messages);
});
Are the any possibility to run separated thread by topic name (consumeResult.Topic)? When consumer receive a message, it should redirect it to the corresponding thread by topic
Give this a go:
Observable
.Interval(TimeSpan.FromSeconds(0.1))
.Take(20)
.GroupBy(x => x % 3)
.SelectMany(xs => Observable.Using(() => new EventLoopScheduler(), els => xs.ObserveOn(els)))
.Subscribe(x => Console.WriteLine($"{x} {Thread.CurrentThread.ManagedThreadId}"));
This ensures that a thread is created in the new EventLoopScheduler() scheduler for each inner observable created by the GroupBy operator. The SelectMany flattens out the group, but keeps the EventLoopScheduler associated for each group.
In your case you GroupBy the consumeResult.Topic property.
Do make sure that your source observable ends as the threads live forever until they do. Calling Dispose() on the subscription is sufficient to end the observable.
I have some test code with GroupBy which works as expected...
Code
var sw = Stopwatch.StartNew();
int groupSize = 5;
var coreObservable = Observable
.Range(1, 20)
.Select((x, idx) => new { x, idx })
.GroupBy(x => x.idx / groupSize, x => x.x)
.Select(x => x.ToList())
.Replay()
.RefCount();
coreObservable.Subscribe(
x => x.Subscribe(y => Console.WriteLine("Event raised [Books: {0}, Timestamp: {1}]", string.Join("|", y), sw.Elapsed)),
() => Console.WriteLine("Subcription closed"));
coreObservable.Wait(); // blocking until observable completes
Output
Event raised [Values: 1|2|3|4|5, Timestamp: 00:00:00.3224002]
Event raised [Values: 6|7|8|9|10, Timestamp: 00:00:00.3268353]
Event raised [Values: 11|12|13|14|15, Timestamp: 00:00:00.3270101]
Event raised [Values: 16|17|18|19|20, Timestamp: 00:00:00.3270803]
Subcription closed
The problem is when I try to use Concat with this expression...
Code
var sw = Stopwatch.StartNew();
int groupSize = 5;
var coreObservable = Observable
.Range(1, 20)
.Select((x, idx) => new { x, idx })
.GroupBy(x => x.idx / groupSize, x => x.x)
.Select(x => x.ToList())
.Concat() // JUST ADDED THIS
.Replay()
.RefCount();
coreObservable.Subscribe(
x => Console.WriteLine("Event raised [Values: {0}, Timestamp: {1}]", string.Join("|", x), sw.Elapsed),
() => Console.WriteLine("Subcription closed"));
coreObservable.Wait(); // blocking until observable completes
Output
Event raised [Values: 1|2|3|4|5, Timestamp: 00:00:00.2728469]
Event raised [Values: , Timestamp: 00:00:00.2791311]
Event raised [Values: , Timestamp: 00:00:00.2793720]
Event raised [Values: , Timestamp: 00:00:00.2794617]
Subcription closed
Notice only the first set of values was exposed.
The reason I am using GroupBy and not Buffer, is because I am trying to use it as a way to create max size chunks for a data feed which comes in bursts. The original observable will probably be arrays of items, where I want to split the arrays when there are too many items in a single event.
The reason I want to use Concat is because I want to be able to create delays between the array events, like many people have recommended here.
Replace Concat() with Merge(), and it works correctly.
I believe the reason for your issue, is that Concat() will not begin listening to the next sequence until the current one completes.
Concat diagram:
s1 --0--1--2-|
s2 -5--6--7--8--|
r --0--1--2--5--6--7--8--|
While Merge() subscribes to all child sequences at the same time, and publishes a value whenever any child publishes a value.
Merge diagram:
s1 --1--1--1--|
s2 ---2---2---2|
r --12-1-21--2|
So in your case, the Concat() subscribes to the first IObservable<IList<int>> from the Select(x => x.ToList()), publishes values until it completes, then subscribes to the next sequence. GroupBy() will create a new IGroupedObservable stream for each group that it finds, however all of the IGroupedObservables will complete at the same time: When the underlying stream completes.
So Concat() listens to the first stream until it completes, but when the first stream completes, all the others have also completed (Since they are all actually the same sequence, just split by key), so there are no values for it to publish for the following sequences.
All diagrams were borrowed from here which is a fantastic resource for Rx, and I highly recommend you look there for any questions about how the various operators work.
Your problem can reduce to something like this, which may be simpler to think on:
var sw = Stopwatch.StartNew();
var subject = new Subject<int>();
var o2 = subject.Where(i => i % 2 == 0).ToList();
var o3 = subject.Where(i => i % 3 == 0).ToList();
var o4 = subject.Where(i => i % 4 == 0).ToList();
var c = Observable.Concat(o2, o3, o4)
// .Replay()
// .RefCount()
//.Replay().RefCount() has no impact here.
;
c.Subscribe(
x => Console.WriteLine("Event raised [Values: {0}, Timestamp: {1}]", string.Join("|", x), sw.Elapsed),
() => Console.WriteLine("Subcription closed"));
for(int i = 0; i < 6; i++)
subject.OnNext(i);
subject.OnCompleted();
Output:
Event raised [Values: 0|2|4, Timestamp: 00:00:00.0002278]
Event raised [Values: , Timestamp: 00:00:00.0002850]
Event raised [Values: , Timestamp: 00:00:00.0003049]
Subcription closed
If you were to marble diagram these, it would like this:
s : 012345|
o2 : ------(024)|
o3 : ------(03) |
o4 : ------(04) |
cOut: ------(024)|
cSub: (So2)------(So3)(So4)
cSub shows when c subscribes to child observables. cOut shows c's output.
So2 means subscribe to o2, So3 means subscribe to o3, etc..
Concat subscribes to the first observable passed to it, then only subscribes to the subsequent observable when the current one is complete. In our case, ToList doesn't omit anything until the source completes, when it dumps the whole list. So o2, o3, o4 all complete simultaneously, but c is only subscribed to o2. After o2 completes, it tries subscribing to the others, but they're already done.
As for how to fix it, Merge would work, but I'm guessing you want to process group 1 before group 2, which Merge would break.
I have ObservableCollection<T> and I need to create observable<bool> which returns true if collection contains any elements
I try to do this
var collectionHasElementsObservable =
Observable.FromEventPattern<NotifyCollectionChangedEventHandler,NotifyCollectionChangedEventArgs>(
ev => ((ObservableCollection<MyType>)_items).CollectionChanged += ev,
ev => ((ObservableCollection<MyType>)_items).CollectionChanged -= ev);
But I don't know how to convert this into IObservable<bool>
How can I create observable<bool> from this?
You can use Select to map the event into one of having elements:
ObservableCollection<int> coll = new ObservableCollection<int>();
var hasElements =
Observable.FromEventPattern<NotifyCollectionChangedEventHandler,NotifyCollectionChangedEventArgs>(
a => coll.CollectionChanged += a,
a => coll.CollectionChanged -= a)
.Select(_ => coll.Count > 0);
Example:
hasElements.Subscribe(Console.WriteLine);
coll.Add(1);
coll.Add(2);
coll.Remove(1);
coll.Remove(2);
Output:
True
True
True
False
Is this what you were looking for?
I notice you have the ReactiveUI tag - were you to be using ReactiveCollection, this would be even easier:
coll.CollectionCountChanged.Select(x => x > 0);