We are relatively new to ReactiveUI so this may explain why we are having some issues with getting a view model working.
In our view model we have a ReactiveList of a class , for which there is a 'selected' in the class.
In the view model we want to have a 'AnySelected' property such that if there is at least 1 item in the list marked as selected then AnySelected is true.
We are having difficulty in getting this work.
As a small test application, with just strings, we have tried this, but messages around changes occurring don't appear.
public class TestRx : ReactiveObject
{
private ReactiveList<string> mySelectedItems;
public ReactiveList<string> MySelectedItems
{
get { return mySelectedItems; }
set { this.RaiseAndSetIfChanged(ref mySelectedItems, value); }
}
public TestRx()
{
var firstList = new ReactiveList<string>();
var t = this.WhenAnyValue(x => x.MySelectedItems);
var t1 = t.Select(x => x ?? new ReactiveList<string>());
var changed = t1.Select(x => x.Changed.Select(_ => Unit.Default));
var itemChanged = t1.Select(x => x.ItemChanged.Select(_ => Unit.Default));
var countChanged = t1.Select(x => x.CountChanged.Select(_ => Unit.Default));
t.Subscribe(x => Debug.WriteLine("T HAS CHANGED {0}", x == firstList));
t1.Subscribe(z => Debug.WriteLine("T1 Changed {0}", z == firstList));
changed.Subscribe(x => Debug.WriteLine("Changed :"));
itemChanged.Subscribe(x => Debug.WriteLine("Item Changed :"));
var replacementList = new ReactiveList<SelItem>(new[] {
new SelItem() { Selected = false }
});
Debug.WriteLine("***********************Assign 1st list");
MySelectedItems = firstList;
Thread.Sleep(100);
Debug.WriteLine("***********************Adding item 2 list");
MySelectedItems.Add("a new string");
// we don't get any debug messages as a result of the above
Thread.Sleep(100);
Debug.WriteLine("***********************Assign null");
MySelectedItems = null;
Thread.Sleep(100);
}
}
What are we doing wrong ?
This is a common pattern, but it's a bit tricky to implement, because you have to handle all of the following scenarios:
The list is set
The list items change
The "Selected" property on any items change. Keep in mind, the items you want to watch, change because of #1 or #2.
How do I do it?
Here's one way to do it. It's complicated, and that hints at a place where future versions of RxUI could make things Better, but here's what you can do for now.
IObservable<bool> WhenAnyAreTrue(IEnumerable<ViewModel> currentElements)
{
// NB: 'Unit' here means, we don't care about the actual value, just
// that something changed
var notifyWhenAnySelectedItemChanges = currentElements
.Select(x => x.WhenAny(y => y.Selected, _ => Unit.Default).Skip(1))
.Merge();
return notifyWhenAnySelectedItemChanges
.StartWith(Unit.Default)
.Select(_ => currentElements.Any(x => x.Selected));
}
// Any time MySelectedItems change or when the items in it change,
// create a new WhenAnyAreTrue and switch to it
this.WhenAnyObservable(x => x.MySelectedItems.ItemsChanged)
.Select(_ => WhenAnyAreTrue(MySelectedItems))
.Switch()
.ToProperty(this, x => x.AnySelected, out anySelected);
Related
I have code which would need to have GroupBy and would need a unique BehaviorSubject per group of Switch().
We have a stream of stock market values that we group by Symbol and perform level crossing across a number of levels (defined by a BehaviorSubject and a switch to always use the latest values).
So I need to go from this:
var feed = new Subject<double>();
var levels = new BehaviorSubject<double[]>(new[] { 400.0, 500.0, 600.0, 700.0 });
levels
.Select(thresholds => feed
.Buffer(2, 1)
.Where(x => x.Count == 2)
.Select(x => new { LevelsCrossed = thresholds.GetCrossovers(x[0], x[1]), Previous = x[0], Current = x[1] })
.Where(x => x.LevelsCrossed.Any())
.SelectMany(x => x.LevelsCrossed.Select(level => new ThresholdCrossedEvent(level, x.Previous, x.Current))))
.Switch()
.Subscribe(x => Console.WriteLine(JsonConvert.SerializeObject(x)));
And adapt the above to take a stream of Tick below and group by Symbol, each with its own level threshold detection on each grouped Value.
class Tick
{
public string Symbol { get; set; } // The name.
public decimal Value { get; set; } // The value.
}
Outline:
Take Market data
Group by Symbol
Alert on levels (depending on group name, using a dictionary of BehaviorSubject)
Output
Use Switch() to always use latest values from the dictionary
With a naive implementation I have a wrapper class (ReactiveSymbolFeed below), however blurring non-reactive and reactive code can introduce potential concurrency issues that reactive extensions otherwise deals neatly with.
Questions please:
Am I introducing any side effects, or will this cause issue at scale (say 100,000 messages per second across 2,000 groups)?
Since we have many groups each with their own BehaviorSubject that needs Switch() - can we rewrite our Reactive Extensions statement block to include the thresholds levels per symbol group, or is the above wrapper class the right way to do this?
Further context and the wrapper class solution
Instead I create a ReactiveSymbolFeed wrapper that will form the value part of a dictionary per symbol key.
class ReactiveSymbolFeed
{
readonly BehaviorSubject<double[]> levels;
readonly Subject<double> feed;
public ReactiveSymbolFeed(double[] levels)
{
this.feed = new Subject<double>();
this.levels = new BehaviorSubject<double[]>(levels);
this.levels
.Select(thresholds => this.feed
.Buffer(2, 1)
.Where(x => x.Count == 2)
.Select(x => new { LevelsCrossed = thresholds.GetCrossovers(x[0], x[1]), Previous = x[0], Current = x[1] })
.Where(x => x.LevelsCrossed.Any())
.SelectMany(x => x.LevelsCrossed.Select(level => new ThresholdCrossedEvent(level, x.Previous, x.Current))))
.Switch()
.DistinctUntilChanged(x => x.Threshold)
.Subscribe(x => Console.WriteLine(JsonConvert.SerializeObject(x)));
}
public void OnNext(double value) => this.feed.OnNext(value);
public void UpdateThresholds(double[] levels) => this.levels.OnNext(levels);
}
And then use with the below:
// Setup the detection thresholds per Symbol - each Symbol has 1 set of thresholds
var dictionary = new Dictionary<string, ReactiveSymbolFeed>();
dictionary.Add("AAPL", new ReactiveSymbolFeed(new[] { 120.0, 125.0, 130.0 }));
dictionary.Add("VXX", new ReactiveSymbolFeed(new[] { 10.5, 15, 18.5, 20 }));
// Create some test tick data.
var ticks = new[]
{
new Tick { Symbol = "AAPL", Value = 119.0 },
new Tick { Symbol = "VXX", Value = 10.3 },
new Tick { Symbol = "VXX", Value = 10.8 },
new Tick { Symbol = "AAPL", Value = 121.0 },
new Tick { Symbol = "AAPL", Value = 121.0 }
// Followed by many other differnet Symbols and Values
};
// Loop through test data and dispatch it.
foreach(var tick in ticks)
{
if(dictionary.TryGetValue(tick.Symbol, out var value))
value.OnNext(tick.Value);
}
I need a Buffer method that doesn't buffer on time or on a certain condition.
It should behave similar to this snapshot method:
Taking a snapshot of ReplaySubject<T> buffer
However it should not take a single snapshot, it should buffer when synchronous changes occur ond provide them as IObservable<IList<T>>.
I think there should be an almost simple solution as this Snapshot method, but I can't get my head around how to really solve this. (Note: The snapshot mehtod also works good for queries over multiple subjects)
Here is a test Method:
[TestMethod]
public async Task SyncBufferTest()
{
var i1 = new BehaviorSubject<int>(1);
var i2 = new BehaviorSubject<int>(4);
var sum = i1.CombineLatest(i2, (i1Value, i2Value) => i1Value + i2Value);
var listAsync = sum.SynchronousBuffer().Select(buf => buf.Last()).ToList().RunAsync(new CancellationToken());
Action syncChange1 = () =>
{
i1.OnNext(2);
i2.OnNext(5);
i1.OnNext(7);
};
Action syncChange2 = () =>
{
i1.OnNext(1);
i2.OnNext(1);
};
Action syncChange3 = () =>
{
i1.OnNext(3);
i1.OnCompleted();
i2.OnCompleted();
};
Task.Run(syncChange1)
.ContinueWith(t => syncChange2())
.ContinueWith(t => syncChange3());
var list = await listAsync;
CollectionAssert.AreEqual(new List<int> { 5, 12, 2, 4 }, list.ToList());
}
Background:
I am working on an architecture concept with a reactive data layer as the base of the application. The whole data layer consists of Subjects (as a "talking" data layer). In a single transaction multiple of these Subjects are changed. I have many Observables in a higher layer of my application that are queries to multiple of these Subjects. So I need this SynchronousBuffer to handle synchronous changes to all of these subjects in all of these queries to not get notified multiple times.
If you're looking for a reactive solution, it's always easier if you model your inputs as observables. In this case:
var i1 = new BehaviorSubject<int>(1);
var i2 = new BehaviorSubject<int>(4);
var sum = i1.CombineLatest(i2, (i1Value, i2Value) => i1Value + i2Value);
Action syncChange1 = () =>
{
i1.OnNext(2);
i2.OnNext(5);
i1.OnNext(7);
};
Action syncChange2 = () =>
{
i1.OnNext(1);
i2.OnNext(1);
};
Action syncChange3 = () =>
{
i1.OnNext(3);
i1.OnCompleted();
i2.OnCompleted();
};
IObservable<Action> actions = new Action[] { syncChange1, syncChange2, syncChange3 }.ToObservable();
Same as the question, just we're structuring our Actions as an observable series of changes. Now, magic can happen:
var openWindow = new Subject<int>();
var closeWindow = new Subject<int>();
var gatedActions = actions
.Select((a, i) => new Action(() => {
openWindow.OnNext(i);
a();
closeWindow.OnNext(i);
}));
Now we have windows defined, which can easily be passed into .Buffer() or .Window().
// alternative to window. Not used.
var buffer = sum.Buffer(openWindow, i => closeWindow.Where(cwi => cwi == i));
var listAsync = sum
.Window(openWindow, i => closeWindow.Where(cwi => cwi == i))
.SelectMany(w => w.TakeLast(1))
.ToList()
.RunAsync(new CancellationToken());
gatedActions.Subscribe(a => a(), () => { openWindow.OnCompleted(); closeWindow.OnCompleted(); });
var list = await listAsync; //output is {12, 2, 4}. The starting 5 can be worked in with a .Merge() or something.
Another approach is to try to define a time window within which you consider changes to be synchronous:
var synchronounsWindow = TimeSpan.FromMilliseconds(100);
var actions = new Action[] {syncChange1, syncChange2, syncChange3};
IObservable<Unit> allChanges = Observable.Merge(
i1.Select(_ => Unit.Default),
i2.Select(_ => Unit.Default)
);
Once we have a time window, you can apply the same windowing/buffering techniques as the other answer.
var buffer = sum.Buffer(allChanges.Throttle(synchronounsWindow)); //alternative to window if you like
IList<int> list = null;
var listAsync = sum
.Window(allChanges.Throttle(synchronounsWindow))
.SelectMany(w => w.TakeLast(1))
.ToList()
.Subscribe(l => { list = l;});
foreach (var a in actions)
{
a();
await Task.Delay(synchronounsWindow);
}
CollectionAssert.AreEqual(new List<int> { 12, 2, 4 }, list.ToList()); // again, skipping 5
I still don't have a good grasp on LINQ yet, and felt like my code could be optimised so looking for help.
I have a Patient and Med Class, each have a public bool IsSelected. These are wrapped into the PatientMeds and PatientsMeds Classes;
public class PatientMeds
{
public Patient Patient;
public List<Med> Meds;
}
public class PatientsMeds
{
public List<PatientMeds> PatientMedsList;
}
I want to filter these, so if the Patient.IsSelected == false then ignore it, and ignore only the Meds where IsSelected == false;
Now, this code works:
List<PatientMeds> patientMedsList = PatientsMeds.PatientMedsList
.Where(x => x.Patient.IsSelected)
.ToList();
foreach (PatientMeds patientMeds in patientMedsList)
{
var medsToRemove = patientMeds.Meds.Where(m => m.IsSelected == false).ToList();
foreach (Med med in medsToRemove)
{
patientMeds.Meds.Remove(med);
}
}
But it just seems 'clunky'. How can i make it better?
I would use ForEach RemoveAll method
List<PatientMeds> patientMedsList = PatientsMeds.PatientMedsList
.Where(x => x.Patient.IsSelected)
.ToList();
patientMedsList.ForEach(p=> p.Meds.RemoveAll(m=>!m.IsSelected));
You could construct a new list with new PatientMeds instances containing only selected patients and meds:
var selectedPatientsWithSelectedMeds = patientMedsList.Where(p => p.IsSelected)
.Select(p => new PatientMeds
{
Patient = p.Patient,
Meds = p.Meds.Where(m => m.IsSelected).ToList()
})
.ToList();
So the Where(p => p.IsSelected) only selects selected patients, and the Select(p => new PatientMeds { ... } constructs new PatientMeds instances.
Finally p.Meds.Where(m => m.IsSelected).ToList() constructs a new list with only selected meds.
But it's not clear whether constructing new PatientMeds and List<Med> instances is viable. For example at new PatientMeds { ... } you will need to map all properties of PatientMeds.
Try shortening the following foreach loop
foreach (PatientMeds patientMeds in patientMedsList)
{
patientMeds.Meds.RemoveAll(m => m.IsSelected == false);
}
You can try RemoveAll
patientsMeds
.PatientMedsList
.Where(m => m.Patient.IsSelected)
.ToList()
.ForEach(m => m.Meds.RemoveAll(med => !med.IsSelected));
As being reference type, despite you create new list using ToList() method, it will point to same location. So, the result also will be reflected at patientsMeds variable
just use:
var bb = patientMedsList.Where(p => p.Patient.IsSelected).ToList().Select(p => new PatientMeds { Patient = p.Patient, Meds = p.Meds.Where(m => m.IsSelected).ToList() }).ToList();
I am constructing a reactive pipeline that needs to expand (SelectMany) and then flatten (in this case, ToArray) whilst maintaining access to a piece of state obtained at the beginning of the pipeline.
Here is pseudo-code for what I am attempting:
return Observable
.Start(() => this.GetSearchResults(query))
.SelectMany(results => results.Hits) // results.Hits is a list of IDs. But there is also has a bool property that I want to keep through to the end of my pipeline
.SelectMany(hit => GetById(hit.Id)) // asynchronously load each result
.ToArray() // now need to pull all the results together into a containing data structure, and also include the bool flag from above in it
.Select(resolvedResults => new ...); // need access to both resolvedResults and the bool mentioned in the first comment above
So I'm trying to find a way to cleanly access some state determined at the beginning of the pipeline from the code at the end of the pipeline.
The first thing I tried was using anonymous types to bundle the bool with each result. This quickly got out of hand and was wasteful from a performance perspective.
The second thing I tried was using a subject as follows:
var state = new AsyncSubject<bool>();
return Observable
.Start(() => this.GetSearchResults(query))
.Do(results =>
{
state.OnNext(results.Flag);
state.OnCompleted();
}
.SelectMany(results => results.Hits)
.SelectMany(hit => GetById(hit.Id))
.ToArray()
.Zip(
state,
(results, state) => new ResultContainer(state, results));
This seems to work fine, but feels a little icky to me.
So what I'm wondering is whether there is a cleaner way to manage state in a reactive pipeline.
For reference, here is the actual code (rather than just pseudo-code):
public IObservable<ISearchResults<IContact>> Search(string query, int maximumResultCount = 100, float minimumScore = 0.1F)
{
Ensure.ArgumentNotNull(query, nameof(query));
var moreHitsAvailable = new AsyncSubject<bool>();
return Observable
.Start(
() => this.searchIndexService.Search<IContact>(query, maximumResultCount, minimumScore),
this.schedulerService.DataStoreScheduler)
.Do(
results =>
{
moreHitsAvailable.OnNext(results.MoreHitsAreAvailable);
moreHitsAvailable.OnCompleted();
})
.SelectMany(
results => results
.Hits
.Select(
hit => new
{
Id = hit.Id,
ParsedId = ContactId.Parse(hit.Id)
}))
.SelectMany(
result => this
.GetById(result.ParsedId)
.Select(
contact => new
{
Id = result.Id,
Contact = contact
}))
.Do(
result =>
{
if (result.Contact == null)
{
this.logger.Warn("Failed to find contact with ID '{0}' provided by the search index. Index may be out of date.", result.Id);
}
})
.Select(result => result.Contact)
.Where(contact => contact != null)
.ToArray()
.Zip(
moreHitsAvailable,
(results, more) => new SearchResults<IContact>(more, results.ToImmutableList()))
.PublishLast()
.ConnectUntilCompleted();
}
You could pop out to Query Comprehension Syntax and do something like this
var x = from result in Observable.Start(() => this.GetSearchResults())
let hasMore = result.MoreHitsAreAvailable
from hit in result.Hits
from contact in GetById(hit.Id)
select new { hasMore , contact};
Over to you how to deal with the duplicate hasMore values. As we know it will be just the single distinct value (all true or all false) you could group by.
I have to poll a database until it contains valid data.
To do it, I have a repository that should queried every n seconds in order to get a my very own entity, called DestinationResponse.
class DestinationResponse
{
bool HasDestination { get; set; }
bool Destination { get; set; }
}
When the DestinationResponse has its property HasDestination to true, the Destination is returned.
So, my observable sequence should get all the responses waiting for one to have HasDestination=true. It basically awaits for a response that HasDestination set to true. When this happens, it returns it and the sequence completes. It will only push one element at most!
My current approach is this:
var pollingPeriod = TimeSpan.FromSeconds(n);
var scheduler = new EventLoopScheduler(ts => new Thread(ts) {Name = "DestinationPoller"});
var observable = Observable.Interval(pollingPeriod, scheduler)
.SelectMany(_ => destinationRepository.GetDestination().ToObservable())
.TakeWhile(response => !response.HasDestination)
.TakeLast(1)
.Select(response => response.Destination);
I know I's wrong, mainly because the Interval call will keep generating even if the last call to GetDestination hasn't finished.
NOTE:
repository.GetDestination() returns a Task<DestinationResponse> and it actually queries the database.
Merging the answer from Database polling with Reactive Extensions with your example code, I think gives you what you want.
var pollingPeriod = TimeSpan.FromSeconds(n);
var scheduler = new EventLoopScheduler(ts => new Thread(ts) {Name = "DestinationPoller"});
var query = Observable.Timer(pollingPeriod , scheduler)
.SelectMany(_ => destinationRepository.GetDestination().ToObservable())
.TakeWhile(response => response.HasDestination)
.Retry() //Loop on errors
.Repeat() //Loop on success
.Select(response => response.Destination)
.Take(1);
This code may be the query I want. What do you think?
private IObservable<Destination> CreateOrderDestinationObservable(string boxId, int orderId)
{
var pollingPeriod = TimeSpan.FromSeconds(DestinationPollingDelay);
var scheduler = new EventLoopScheduler(ts => new Thread(ts) {Name = "DestinationPoller"});
var observable = Observable.Timer(pollingPeriod, scheduler)
.SelectMany(_ => externalBridgeRepository.GetDestination(boxId, orderId).ToObservable())
.Where(response => response.HasDestination)
.Retry()
.Repeat()
.Take(1)
.Select(response => response.Destination);
return observable;
}