Reputation: 331
I'm currently creating a program that reads out data sent via a COM port and then plots it live in a diagram. The data is displayed using the MVVM principle, which works fine when data is sent at around 10Hz. However, the device the data is being read from can go up to a refresh rate of 1 kHz, which means 1000 datasets per minute. This works fine for displaying and updating simple textboxes, however it breaks the diagram because the updating is happening too fast.
What I think I need to do now is limit the amount of update events that is sent to the subscribed classes and pages, so that only a limited amount of data is sent through, which gives the diagram a chance to draw properly. Is there a way to limit this automatically, or what code adjustments would you suggest to do just that manually?
A small code snippet from my collection changed event:
void dataItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("dataItems");
NotifyPropertyChanged("lastItem");
// update any charts
NotifyPropertyChanged("AccelXData");
NotifyPropertyChanged("AccelYData");
NotifyPropertyChanged("AccelZData");
}
// handle property changes
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
Every dataset also has an ID that maybe can be used to check when to update manually, as an idea.
Upvotes: 4
Views: 1794
Reputation: 16554
Both the existing answers make valid points, as this is becoming a dup origin, its time to add some more context that might make this more relevant to future duplicate references
In an MVVM world this is a common pattern, especially when you have defined some read-only properties that resolve expressions and are not backed by a property. Under normal use the forced calls to NotifyPropertyChanged()
probably do not cause concern, but when you are loading a large record set incrementally or performing operations against a collection it can be useful to disable updates until the end of the operation:
/// <summary>
/// Example of a readonly expression backed property,
/// created to simplify MVVM bindings.
/// </summary>
public object lastItem { get => dataItems?.Last(); }
/// <summary>Flag to disable <see cref="NotifyPropertyChanged(string)"/> for collection related fields</summary>
private bool suppressCollectionUpdates = false;
void dataItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (!suppressCollectionUpdates)
{
NotifyPropertyChanged(nameof(dataItems));
NotifyPropertyChanged(nameof(lastItem));
// update any charts
NotifyPropertyChanged(nameof(AccelXData));
NotifyPropertyChanged(nameof(AccelYData));
NotifyPropertyChanged(nameof(AccelZData));
}
}
/// <summary>
/// A long running operation that causes the UI to update too frequently
/// </summary>
void Operation()
{
suppressCollectionUpdates = true;
try
{
... Do long running or incremental changes to the dataItems
}
finally
{
// leave it back in the default state
suppressCollectionUpdates = false;
// Call the change event manually, use the Reset value to indicate a dramatic change ocurred.
// You could also send null because our code does not use these references anyway ;)
dataItems_CollectionChanged(dataItems, new System.Collections.Specialized.NotifyCollectionChangedEventArgs(System.Collections.Specialized.NotifyCollectionChangedAction.Reset));
}
}
// handle property changes
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
{
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
If this is MVVM, you would probably bind some sort of progress spinner or other form of visual feedback to this suppressCollectionUpdates
, in that case you name the flag something more appropriate like IsBusy
or IsLoading
and set that up with the backing field and call to NotifyPropertyChanged
.
Another option for long running operations and high frequency changes is that you could introduce a timer to periodically call the refresh:
private void notifyCollectionChangeTimer_Tick(object sender, EventArgs e)
{
suppressCollectionUpdates = false;
dataItems_CollectionChanged(dataItems, new System.Collections.Specialized.NotifyCollectionChangedEventArgs(System.Collections.Specialized.NotifyCollectionChangedAction.Reset));
suppressCollectionUpdates = true;
}
/// <summary>
/// A long running operation that causes the UI to update too frequently
/// </summary>
/// <remarks>A timer will call refresh periodically</remarks>
void Operation()
{
suppressCollectionUpdates = true;
DispatcherTimer timer = new DispatcherTimer();
try
{
timer.Interval = TimeSpan.FromSeconds(1); // how often to refresh the UI
timer.Tick += notifyCollectionChangeTimer_Tick;
timer.Start();
... Do long running or incremental changes to the dataItems
}
finally
{
_refreshTimer.Stop(); // stop timer
_refreshTimer.Tick -= OnTick; // unsubscribe from timer's ticks (just in case you move the timer to a parent scope ;)
// leave it back in the default state
suppressCollectionUpdates = false;
// Call the change event manually, use the Reset value to indicate a dramatic change ocurred.
// You could also send null because our code does not use these references anyway ;)
dataItems_CollectionChanged(dataItems, new System.Collections.Specialized.NotifyCollectionChangedEventArgs(System.Collections.Specialized.NotifyCollectionChangedAction.Reset));
}
}
Upvotes: 0
Reputation: 841
A better approach would to remove the calls to NotifyPropertyChanged whenever the data changes.
Create a timer and refresh on the timer. That way you can control the refresh rate, and it is not bound to the rate at which the data arrives.
Upvotes: 2
Reputation: 12811
This isn't a complete answer, but something to note:
I see that you're doing NotifyPropertyChanged("dataItems")
from within your CollectionChanged
handler. I don't think you want to do this, and it may be causing a performance issue. dataItems
appears to be a property that is of type ObservableCollection<T>
. When the collection is changed, the collection itself sends a CollectionChanged
event. In your UI, an ItemsControl
(ComboBox
, ListBox
, etc) is probably bound to the dataItems
property. When the collection raises its CollectionChanged
event, you cannot guarantee the order in which the event handlers will be called. If your UI handles the event first, it may try to allocate/deallocate the containers and UI elements for the new/old items in your collection. When you manually call NotifyPropertyChanged("dataItems")
, the UI may discard all UI elements and reconstruct them (depending on whether the UI element is smart enough to recognize that the value hasn't changed, and also depending on the container recycling logic). This is (obviously) inefficient. Don't ever send PropertyChanged
notifications unless the returned value/object of the property changes.
Make this change and let us know if there is any significant impact.
Upvotes: 1