每秒限制绑定更新

4
我正在创建一个程序,它可以通过COM端口读取数据并实时在图表中绘制出来。使用MVVM原则显示数据时,当数据以大约10Hz发送时,这种方法运行良好。然而,从中读取数据的设备可以达到1kHz的刷新频率,即每分钟1000个数据集。这对于显示和更新简单文本框来说很好,但是由于更新发生得太快,所以破坏了图表。
我认为我现在需要做的是限制发送给订阅类和页面的更新事件数量,以便只发送有限量的数据,这样图表就有机会正确绘制。是否有一种自动限制的方法,或者您建议进行哪些代码调整以手动完成此操作?
下面是我的收集事件中的一个小代码片段:
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));
}

每个数据集也有一个ID,可以用来手动检查更新的时间,这是一个想法。
3个回答

2
更好的方法是在数据发生变化时删除对NotifyPropertyChanged的调用。创建一个计时器并在计时器上刷新。这样您就可以控制刷新速率,而不受数据到达速率的限制。

我将此标记为答案,但我卡在了MVVM模型上,并强制以10 Hz一致地更新图表事件。为此,我只需检查最后一个dataItemid是否是refreshRate/10的倍数。这会增加一些延迟,但与安全变量结合使用时,可以防止崩溃,该变量不会被不断更新。 - Heiko Rothe

1
这不是一个完整的答案,但需要注意的是:
我发现您在CollectionChanged处理程序中调用了NotifyPropertyChanged("dataItems")。我认为您不应该这样做,这可能会导致性能问题。 dataItems似乎是一个ObservableCollection<T>类型的属性。当集合被更改时,集合本身会发送CollectionChanged事件。在您的UI中,可能将ItemsControl (ComboBox, ListBox等)绑定到dataItems属性。当集合引发其CollectionChanged事件时,无法保证按照哪个顺序调用事件处理程序。如果您的UI首先处理事件,则它可能会尝试分配/取消分配新/旧项的容器和UI元素。当您手动调用NotifyPropertyChanged("dataItems")时,UI可能会丢弃所有UI元素并重建它们(取决于UI元素是否足够聪明以识别值未更改,以及容器回收逻辑)。这是(显然)低效的。除非属性的返回值/对象发生更改,否则永远不要发送PropertyChanged通知。

进行这个更改并告知我们是否有任何重大影响。


没有给出任何重大的性能改进,但这是一个有效的观点,谢谢! - Heiko Rothe

0
两个现有的答案都提出了有效的观点,由于这已经成为一个重复的来源,现在是时候添加一些更多的上下文,以使这更相关于未来的重复引用。
在MVVM世界中,这是一种常见的模式,特别是当您定义了一些只读属性来解析表达式并且没有被属性支持时。在正常使用中,对NotifyPropertyChanged()的强制调用可能不会引起关注,但是当您逐步加载大量记录集或对集合执行操作时,禁用更新直到操作结束可能很有用:
/// <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));
}

如果这是MVVM,您可能会将某种进度旋转器或其他形式的视觉反馈绑定到此suppressCollectionUpdates,在这种情况下,您可以将标志命名为更合适的名称,例如IsBusyIsLoading,并使用后备字段设置它,并调用NotifyPropertyChanged
另一个长时间运行的操作和高频率更改的选项是,您可以引入一个计时器来定期调用刷新:
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));
    }
}

类似的问题和进一步阅读:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接