Real-time UI Update

I need to create a WPF user interface that is subject to real-time update of the Fx Rate (Currency + rate) and displays them in the grid (approximately 1000 updates per second, which means that each line in the grid can be updated upto 1000 times per second ) At any given time, the grid must have at least 50 rows.

To this, I created a Viewmodel that subscribes to update events and saves these updates inside a parallel dictionary with the key as a symbol and value as a RateViewModel object. Then I have another observable collection that has all these rateviewmodel objects and snaps it to the grid.

the code:

public class MyViewModel { private readonly IRatesService ratesService; private readonly ConcurrentDictionary<string, RateViewModel> rateDictionary; private object _locker = new object(); public MyViewModel(IRatesService ratesService) { this.ratesService = ratesService; this.ratesService.OnUpdate += OnUpdate; rateDictionary = new ConcurrentDictionary<string, RateViewModel>(); RateViewModels = new ObservableCollection<RateViewModel>(); } private void OnUpdate(object sender, RateUpdateEventArgs e) { RateViewModel exisistingRate; if (!rateDictionary.TryGetValue(e.Update.Currency, out exisistingRate)) { exisistingRate = new RateViewModel(new Rate(e.Update.Currency, e.Update.Rate)); rateDictionary.TryAdd(e.Update.Currency, exisistingRate); return; } lock (_locker) { exisistingRate.UpdateRate(e.Update.Rate); } Application.Current.Dispatcher.BeginInvoke(new Action(() => SearchAndUpdate(exisistingRate))); } public ObservableCollection<RateViewModel> RateViewModels { get; set; } private void SearchAndUpdate(RateViewModel rateViewModel) { //Equals is based on Currency if (!RateViewModels.Contains(rateViewModel)) { RateViewModels.Add(rateViewModel); return; } var index = RateViewModels.IndexOf(rateViewModel); RateViewModels[index] = rateViewModel; } } 

I have 4 questions:

  • Is there a way to eliminate ObservableCollection, since it leads to the creation of two different data structures storing the same elements, but still my updates are passed to the user interface?

  • I used the Concurrent Dictionary, which blocks the entire update operation. Is there any other smart way to handle this and not block the entire document or, for that matter, any data structure?

  • My UpdateRate method also blocks - all my properties on my RateviewModel are read only with the exception of the price, as this is being updated. Is there any way to make this atomic, please note that the price comes as double.

  • Is there a way to optimize the SearchAndUpdate method, this refers to 1st. At the moment, I consider this an O (n) operation.

Using .NET 4.0 and for brevity omitted INPC.

* EDIT: * Could you help me rewrite this better, given all 4 questions? Psedocode will do.

Thanks, -Mike

+7
c # wpf
source share
3 answers

1) I would not worry about 50 extra refs floating around

2) Yes, locked data structures are executed. Interlocked is your friend here, and they are almost all one. ReaderWriterLock is another good option if you do not change which items in your dictionary often.

3) As a rule, if you are dealing with more data than the data that the user interface can process, you will want to do updates in the background, only run INPC in the user interface stream and, more importantly, be able to refuse User Interface Updates (when updating the support field). The basic approach would be something like this:

  • Make Interlocked.Exchange in the support field
  • Use Interlocked.CompareExchange to set the private field to 1 if it returns 1 output, because there is still a pending user interface update.
  • If Interlocked.CompareExchange returns 0, it is called in the user interface and fires your changed property event and updates your throttling field to 0 (technically you need to do more if you care about non x86)

4) SearchAndUpdate It seems superfluous ... UpdateRate should bubble up in the user interface, and you just need to call Invit to the UI thread if you need to add or remove an element to the observed collection.

Update: here is an approximate implementation ... things are a little more complicated, because you use doubles that do not get atomicity for free on 32-bit processors.

 class MyViewModel : INotifyPropertyChanged { private System.Windows.Threading.Dispatcher dispatcher; public MyViewModel(System.Windows.Threading.Dispatcher dispatcher) { this.dispatcher = dispatcher; } int myPropertyUpdating; //needs to be marked volatile if you care about non x86 double myProperty; double MyPropery { get { // Hack for Missing Interlocked.Read for doubles // if you are compiled for 64 bit you should be able to just do a read var retv = Interlocked.CompareExchange(ref myProperty, myProperty, -myProperty); return retv; } set { if (myProperty != value) { // if you are compiled for 64 bit you can just do an assignment here Interlocked.Exchange(ref myProperty, value); if (Interlocked.Exchange(ref myPropertyUpdating, 1) == 0) { dispatcher.BeginInvoke(() => { try { PropertyChanged(this, new PropertyChangedEventArgs("MyProperty")); } finally { myPropertyUpdating = 0; Thread.MemoryBarrier(); // This will flush the store buffer which is the technically correct thing to do... but I've never had problems with out it } }, null); } } } } public event PropertyChangedEventHandler PropertyChanged = delegate {}; } 
+4
source share

Mike -

I would approach this a little differently. You really don't need the Observable Collection unless new Fx rows are added. The observable collection, as you know, only provides built-in notification of changes in this scenario. If you have a list of 50 lines (for example), and the Fx object (which represents each individual line) is updated 1000 times per second - then you can very well use INotifyPropertyChanged in the Fx properties on the object and update this user interface mechanism as they are changes. My line of thought is a simpler approach to updating the user interface, rather than moving them from one collection to another.

Now regarding your second point - 1000 updates per second (for an existing FX object), which is technically not readable from the point of view of the user interface - the approach I took is freezing and thawing, which means that you essentially intercept InotifyPropertyChanged ( like starting it in the user interface) and maintaining it at a frequency - for example, every 1 second - regardless of my status of all FX objects (update the user interface). Now during this second - no matter what updates occur with the FX properties - they save the rewriting on themselves - and the last / correct value when the 1st interval occurs is displayed in the user interface. Thus, the data displayed in the user interface is always correct and relevant when it is displayed in the user interface.

+3
source share

There are several factors to consider, especially if the number of displayed bids will dynamically change. I assume that 1000 updates / sec come from a thread other than the user interface thread.

First, you will need to configure updates for the user interface stream made for you, for updates to an existing ViewModel, but not for you for new / deleted ViewModels. With 1000 updates per second, you probably want to control the granularity of the marshalling in the user interface stream and the context switching that this entails. Ian Griffiths wrote a great blog series about it.

Secondly, if you want your UI to remain responsive, you probably want to avoid as many Gen 2 garbage collectors as possible, which means minimizing pressure on the GC. This may be a problem in your case when creating a new update of the Rate object for each update.

Once you start having several screens that do the same, you need to find a way to abstract this update into a common component. In other words, you will be scattering stream code through your ViewModels, which are error prone.

I created an open source project, ReactiveTables , which addresses these three issues and adds several other features, such as the ability to filter, sort, join model collections. There are also demos showing how to use it with virtual grids to get better performance. Perhaps this can help you / inspire you.

0
source share

All Articles