I'm having problems figuring out how to do this. I have two instances (source & target) that implement INotifyPropertyChanged and I'm tracking the PropertyChanged event for both. What I want to do is run an action any time source.PropertyChanged is raised until target.PropertyChanged is raised. I can do that just fine like this:
INotifyPropertyChanged source;
INotifyPropertyChanged target;
var sourcePropertyChanged = Observable
.FromEvent<PropertyChangedEventArgs>(source, "PropertyChanged")
.Where(x => x.EventArgs.PropertyName == sourcePropertyName);
var targetPropertyChanged = Observable
.FromEvent<PropertyChangedEventArgs>(target, "PropertyChanged")
.Where(x => x.EventArgs.PropertyName == targetPropertyName);
sourcePropertyChanged
.TakeUntil(targetPropertyChanged)
.ObserveOnDispatcher()
.Subscribe(_ => /*Raises target.PropertyChanged for targetPropertyName*/);
The problem I'm having is I want to ignore the PropertyChanged notifications caused by the actions and only stop taking values when the PropertyChanged event is raised by an external source. Is there a good way to get that to happen?
There's no built in way of doing what you're talking about. Here's a simple SkipWhen implementation that skips the next source value each time a value is received on the 'other' sequence:
public static IObservable<TSource> SkipWhen(this IObservable<TSource> source,
IObservable<TOther> other)
{
return Observable.Defer<TSource>(() =>
{
object lockObject = new object();
Stack<TOther> skipStack = new Stack<TOther>();
other.Subscribe(x => { lock(lockObject) { skipStack.Push(x); });
return source.Where(_ =>
{
lock(lockObject);
{
if (skipStack.Count > 0)
{
skipStack.Pop();
return false;
}
else
{
return true;
}
}
});
});
}
You're code would then be updated like so (see my note below):
INotifyPropertyChanged source;
INotifyPropertyChanged target;
// See the link at the bottom of my answer
var sourcePropertyChanged = source.GetPropertyChangeValues(x => x.SourceProperty);
// Unit is Rx's "void"
var targetChangedLocally = new Subject<Unit>();
var targetPropertyChanged = target.GetPropertyChangeValues(x => x.TargetProperty)
.SkipWhen(targetChangedLocally);
sourcePropertyChanged
.TakeUntil(targetPropertyChanged)
.ObserveOnDispatcher()
.Subscribe(_ =>
{
targetChangedLocally.OnNext();
/*Raises target.PropertyChanged for targetPropertyName*/
});
NB: I recently blogged about a strongly typed IObservable wrapper around INotifyPropertyChanged events; feel free to steal that code.
There's no built-in way but you could probably filter out events using the Where extension method for observable. The condition to filter on would be the sender of the event. I suppose that the sender of a target.PropertyChanged event is different than the sender of a PropertyChanged event raised by another source.
I'm not entirely sure if this is an approach you can use.
Using locks in Rx this way is fine. The lock is short lived and doesn't call out to user code.
Related
There is a way to wrap an event as observable using Observable.FromEvent. E.g. this class:
class Generator<T>
{
event Action<T> onPush;
public IObservable<T> Items =>
Observable.FromEvent<T>(d => onPush += d, d => onPush -= d);
public void Push(T item) => onPush?.Invoke(item);
}
However, I haven't found a way to complete the observable also by an event - how can I do that?
Update:
To clarify what I mean, the class above produces IObservable<T> which is "endless" and never completes. I want to make it completed by another event, not to make another observable. So the question can be reduces to this:
How to make an arbitrary IObservable<T> completed prematurely, i.e. the OnCompleted notification to be called?
An observable represents a stream of notifications, or events. When an observable sources from an event, they are inherently endless. The observable connects to the event, referencing the object, so the object backing the event will never go out of scope. .NET/C# doesn't provide a way to indicate that an event will never be called again, so the observable directly connecting to the event is endless.
This is not uncommon; most event-based observables never have OnCompleted called explicitly, modelling the real world where it is quite hard to say definitively that something will never happen again.
However, this isn't a problem: Observables are meant to run infinitely, and cause no damage. An unsubscribed observable doesn't take up much resources. If you're not interested in an event-sourced observable, unsubscribe all subscriptions and you're fine.
One way to do this is with one of the Take operators, like the TakeUntil operator (as mentioned below). Try the following code (using your Generator class):
var g = new Generator<int>();
g.Items
.TakeUntil(i => i > 3)
.Subscribe(
i => Console.WriteLine($"OnNext: {i}"),
e => Console.WriteLine($"OnError: Message: {e.Message}"),
() => Console.WriteLine("OnCompleted")
);
g.Push(1);
g.Push(2);
g.Push(3);
g.Push(4);
g.Push(5);
g.Push(6);
Output:
OnNext: 1
OnNext: 2
OnNext: 3
OnNext: 4
OnCompleted
TakeUntil unsubscribes from the Items observable after there's a message with an integer larger than 3. This is why there's an OnCompleted, and no 5, 6 messages.
Also, as Enigmativity mentioned, your Generator<T> class is basically the same as Subject<T>, I suggest you use that.
Original answer:
Make another observable from the event, then use .TakeUntil:
class Generator<T>
{
event Action<T> onPush;
event Action<Unit> onCompleted;
public IObservable<T> Items =>
Observable.FromEvent<T>(d => onPush += d, d => onPush -= d)
.TakeUntil(Completion);
public IObservable<Unit> Completion =>
Observable.FromEvent<Unit>(d => onCompleted += d, d => onCompleted -= d);
public void Push(T item) => onPush?.Invoke(item);
public void Complete() => onCompleted?.Invoke(Unit.Default);
}
I am refactoring some code.
Can someone tell me why does my binding in the viewModel stop updating if I comment out those two lines:
.SubscribeOn(ThreadPoolScheduler.Instance)
.ObserveOn(Application.Current.Dispatcher)
How come it affect the second subscribe?
RendererService.WhenRenderProgress.Subscribe
My goal is to remove the wrapper Observable.Create(observer... but when I comment it out, even if the subject emit values, the viewModel does not render them.
Thank you!
public class RendererService
{
public Subject<int> WhenRenderProgress = new Subject<int>();
public void Render()
{
Observable.Create<Unit>(observer =>
{
for (var i = 0; i < 100; i++)
{
WhenRenderProgress.OnNext(i);
}
observer.OnCompleted();
return Disposable.Empty;
})
.SubscribeOn(ThreadPoolScheduler.Instance)
.ObserveOn(Application.Current.Dispatcher)
.Subscribe();
}
}
public class ViewModel: Screen, IViewModel, IDisposable
{
public int Progress { get; set; }
public ViewModel(RendererService RendererService)
{
RendererService.WhenRenderProgress.Subscribe(i =>
{
Progress = i;
NotifyOfPropertyChange(() => Progress);
});
}
}
EDIT:
Sorry, your comments make sense. It is due to WPF threading requirements. The second subscribe has to happen on the UI thread. The best way for that to happen is to change this line...
RendererService.WhenRenderProgress.Subscribe(i =>
to this...
RendererService.WhenRenderProgress.ObserveOn(Application.Current.Dispatcher).Subscribe(i =>
Once you make that change, you can remove the SubscribeOn and ObserveOn calls by the first subscription.
All this happens because Reactive IObservables don't care which thread they're observed on. If your event starts on a background thread, and all the operators are synchronous (which they are here), then it will be observed on the same thread.
The way you have it laid out, this is impossible to reproduce.
If I had to guess, I would guess that there's some exception being thrown in the .Render function, that blows up the first subscription. Since there's no other subscription, the rest of the Observable.Create never happens, since observables only do stuff when there is at least one subscription. The second subscription isn't subscribed to the producing observable, it's listening to a side-effect.
I would recommend you try changing the first subscription call from
.Subscribe();
to
.Subscribe(item => {}, e => LogException(e));
or something like that. This way you can see what's going wrong.
I wonder if it is necessary to check the
NotifyCollectionChangedAction enum of the NotifyCollectionChangedEventArgs, when subscribing to the CollectionChanged event. Every example I stumbled upon does it like this:
myCollection.CollectionChanged += (sender, eventArgs) =>
{
if (eventArgs.Action == NotifyCollectionChangedAction.Add)
{
foreach (SampleClass sampleObject in eventArgs.NewItems)
{
addAction(sampleObject);
}
}
else if (eventArgs.Action == NotifyCollectionChangedAction.Remove)
{
foreach (SampleClass sampleObject in eventArgs.OldItems)
{
removeAction(sampleObject);
}
}
// ...
};
Is it possible to ignore the NotifyCollectionChangedAction and just simplify the code like this:
myCollection.CollectionChanged += (sender, eventArgs) =>
{
eventArgs.NewItems?.OfType<SampleClass>()
.ToList()
.ForEach(addAction);
eventArgs.OldItems?.OfType<SampleClass>()
.ToList()
.ForEach(removeAction);
};
What are the downsides of this idea? Is there anything I have missed?
It depends on what you are trying to do, because those code samples are not equivalent. There are more action types than just Add and Remove. For example there is Replace action. If I do this:
myCollection[0] = new MyObject();
CollectionChanged will be fired with action type Replace, OldItems will contain replaced item (old myCollection[0]) and NewItems will contain new MyObject() item. First code sample will completely ignore this event. Second code sample will handle both items with addAction and removeAction. If you do:
myCollection.Move(0,1);
It will fire event with action Move where both OldItems and NewItems will contain moved item. First sample again will ignore it and second will perform addAction and removeAction on the same item being moved, which might lead to surprising results I guess.
I have a class Foo with an event that publishes a FooState enum. I want to turn this event into an observable that replays the last value for new subscribers.
Even if there are no subscribers, any new subscriber should get the last value.
public enum FooState
{
Stopped = 0,
Starting = 1,
Running = 2,
}
public delegate void FooEventHandler(object sender, FooEventArgs e);
public class FooEventArgs : EventArgs
{
public FooEventArgs(FooState fooState)
{
this.State = fooState;
}
public FooState State {get; private set;}
}
public class Foo
{
public event FooEventHandler FooEvent;
public void OnFooEvent(FooState state)
{
var fooEvent = FooEvent;
if(fooEvent != null)
{
fooEvent(this, new FooEventArgs(state));
}
}
}
My attempts so far revolved around using Publish, RefCount and Replay. But none of the combinations I tried work if I subscribe to the observable after I fire the event.
Replay(1).RefCount() works as long there is already at least one subscription but I need to work for the first late subscription as well.
var foo = new Foo();
var obs = Observable.FromEventPattern<FooEventHandler, FooEventArgs>(
h => foo.FooEvent += h,
h => foo.FooEvent -= h)
.DistinctUntilChanged()
.Replay(1)
.RefCount();
// Works if this line is uncomented.
//obs.Subscribe(x => Console.WriteLine("Early Subscriber = " + x.EventArgs.State));
foo.OnFooEvent(FooState.Running);
obs.Subscribe(x => Console.WriteLine("Late Subscriber = " + x.EventArgs.State));
Does anyone know how to do this with Rx?
RefCount connects only after the first subscription. If you want to have fine grained control of when the connection occurs you should use Replay + Connect.
So do instead:
var publishedSource = eventSource.DistinctUntilChanged().Replay(1);
var connection = publishedSource.Connect();
//Subscribe to publishedSource to receive events and dispose of
connection when you are done.
Posted from my phone so apologies for any syntax errors in advance.
Rx is doing the right thing converting your event notifications to your stream and replaying them, but what you are asking is:
"Why when I subscribe to the event, don't I get the initial state".
Events don't work like that. If I do a += on foo.FooEvent, I don't get an immediate trigger with the current value. I only get notified when it changes.
As you have noticed, 'Replay' will replay subsequent events, but not provide the state at the time of subscription.
To solve your problem, you'll need to ensure that the current value is put onto the stream before you hook up the stream for change notifications.
Check out Observable.StartWith().
i.e. Do ".StartWith(foo.State)" before the.DistinctUntilChanged() call (immediately after the .FromEventPattern).
Heres a quick question. I have an ObservableCollection<IItem> where IItem has a property called Id. Throughout the lifetime of an application items are added, removed and then re-added once again to this collection.
What I need is to track when items with certain id's are present in this collection. When all required dependencies are present, I need to do some initialization, if at least one of the required items is removed, then I need to do a cleanup. If that item is then re-added once again, then I need to do initialization again.
Any suggestions what RX operators to use to build such kind of a query?
Keeping track of the state of the collection will probably be somewhat tedious. Unless your collection is very big you can instead examine the collection on each change to determine if your criteria for initialization is fulfilled. Then you can use DistinctUntilChanged to get an observable that will fire when you need to perform initialization and cleanup
Here is an example:
var collection = new ObservableCollection<Int32>();
var observable = Observable
.FromEventPattern<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(
handler => collection.CollectionChanged += handler,
handler => collection.CollectionChanged -= handler
);
You then need a predicate that determines if initialization is required (the collection "is ready"). This predicate can get expensive if your collection is big because it will be called on each change to the collection, but my assumption is that this is not a problem.
Boolean IsReady(IEnumerable<Int32> items, IReadOnlyList<Int32> itemsRequiredToBeReady) {
return items.Intersect(itemsRequiredToBeReady).Count() == itemsRequiredToBeReady.Count;
}
Then you can use DistinctUntilChanged to get notifications when the IsReady predicate changes from true to false and vice versa:
var isReadyObservable = observable
.Select(ep => IsReady((ObservableCollection<Int32>) ep.Sender, ItemsRequiredToBeReady))
.DistinctUntilChanged();
To initialize and cleanup you need two subscriptions:
isReadyObservable.Where(isReady => isReady).Subscribe(_ => Initialize());
isReadyObservable.Where(isReady => !isReady).Subscribe(_ => Cleanup());
ObservableCollection is not quite observable as it turns out, so first you must consider what is the strategy you are going to employ in this case. If it is just added and removed items than this code should suffice.
internal class Program
{
private static ObservableCollection<IItem> oc = new ObservableCollection<IItem>();
private static readonly long[] crossCheck = {1,2,3};
private static void Main(string[] args)
{
oc.CollectionChanged += oc_CollectionChanged;
oc.Add(new IItem {Id=1,Amount = 100});
oc.Add(new IItem {Id=2,Amount = 200});
oc.Add(new IItem {Id=3,Amount = 300});
oc.RemoveAt(1);
}
private static void oc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Console.WriteLine("{0} {1}", e.Action, oc.Sum(s1 => s1.Amount));
if (crossCheck.SequenceEqual(oc.Select(s1 => s1.Id).Intersect(crossCheck)))
Console.WriteLine("I have all elements I wanted!");
if (e.OldItems != null && e.Action.Equals(NotifyCollectionChangedAction.Remove) &&
e.OldItems.Cast<IItem>().Any(a1 => a1.Id.Equals(2))) Console.WriteLine("I've lost item two");
}
}
internal class IItem
{
public long Id { get; set; }
public int Amount { get; set; }
}
Produces:
Add 100
Add 300
Add 600
I have all elements I wanted!
Remove 400
I've lost item two
Press any key to continue . . .
Of course in your event handler you can process other conditions as needed, for example you probably want to fire some of those data dependent events just once, etc.