简体   繁体   中英

Limit binding updates per second

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.

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.

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.

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));
    }
}

Similar Questions and Further reading:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM