在处理大量数据时,INotifyPropertyChanged陷入困境

时间:2016-10-27 11:57:14

标签: c# wpf inotifypropertychanged

我有一个HID设备,我在200hz-600hz左右与之通信,并将数据解释为代表HID设备属性的类对象。该类在其属性上实现了INotifyPropertyChanged,并且由于通信速度的原因,我认为处理队列陷入困境,因为控件在几分钟后似乎变得迟钝和“框架”。

.net中是否存在可能有助于此类问题的方法,可能是事件处理程序池或某种类型的队列?

不幸的是,如果没有我的HID设备,我不确定我的代码对任何人有任何用处都可以复制,但是包含一些相关的代码片段只是为了显示我的实现:

public enum DataEvents { onNone = 0, onStatus = 1, onInput = 2, onOutput = 4, onReport = 8};
public class Controller: INotifyPropertyChanged, IDisposable, INotifyDisposed
{
    public event PropertyChangedEventHandler PropertyChanged;
    public event EventHandler Disposing;
    public event EventHandler Disposed;
    public event EventHandler ReportReceived;

    internal void callPropertyChanged(string PropertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
    }

    internal void callReportReceived()
    {
        ReportReceived?.Invoke(this, EventArgs.Empty);
    }

    public bool Touch1
    {
        get { return _Touch1; }
        private set { if (value != _Touch1) { _Touch1 = value; if (RaiseUpdateEvents.HasFlag(DataEvents.onInput)) callPropertyChanged("Touch1"); } }
    }
    private bool _Touch1 = false;

    //There are many more properties but all of them follow this pattern, and have several different types
}

我的对象是从循环上的System.Threading.Thread填充,轮询HID设备以获取报告.HID设备读取方法是阻塞调用,因此循环不是死循环,并且仅限于数据速率该装置,通常说200hz-600hz。

编辑:值得注意的是,我对WPF绑定特别感兴趣。

3 个答案:

答案 0 :(得分:7)

在处理WPF中的近实时系统时(我花了6年的时间研究它们),你有几个选择。首先,我会列出一些值得思考的食物:

  • 要通过一个事件让所有WPF绑定更新,请使用string.Empty作为您的媒体资源名称。
  • 您的问题可能不完全是由于事件造成的。 WPF有很多影响内存管理的问题。

所以你要问的问题是用户需要多久才能看到任何类型的变化?人类视觉持久性是1/10秒,或100ms。任何比这更频繁的更新都会被浪费,但更多时候,即使这种情况太频繁。

每秒一个事件?

在我的场景中,我们确定我们只需要每秒更新屏幕上的所有内容。即使我们每秒收到12次数据(每个样本83毫秒),我们收集并平均数据以使其平滑。它更好地了解了我们的用户正在发生的事情。

  • 我们构建了我们的视图模型,使用主计时器每秒调用一次Update()方法。
  • 模型已实施INotifyPropertyChanged以避免binding memory leak,但仅使用string.Empty引发了一个属性更改事件以使UI刷新

尽量减少对象创建

垃圾收集所花费的每毫秒都是用户可以与您的应用程序进行交互的明显时间。每次举起活动时,都必须创建要发送的活动对象。虽然从技术上讲,您可以创建一次事件对象并引发相同的实例,但WPF会为您创建对象实例。这些是你需要关注的事情:

  • DataTemplate实际上为您要模板化的每个对象创建了一个新模板。尽可能尝试使用虚拟化,并尽量减少使用虚拟化。
  • ResourceDictionary每次在控件中声明资源字典时,都要创建新实例。在App.xaml中包含所有合并的资源字典比在不同的用户控件中包含相同的字典更好。特别是如果您拥有DataTemplate
  • 中的用户控件
  • ContentPresenter不是你的朋友。

为了进一步说明,ContentPresenter会抓住您的对象,在控件的ResourceDictionary中查找它的类型,找到DataTemplate来实例化你的数据。当您需要将窗口的特定部分换成另一个控件时,这可能很方便,但是它确实需要付出很大的代价。尽可能减少它的使用。

在后台保留硬件/通讯

我们专门设置线程来处理通信和处理需求。这使得UI可以保持响应,同时我们可以对数据进行一些DSP /统计减少。

使用Memory Profiler

每当您需要在显示器上进行近实时更新时,您必须特别注意内存使用。这是我们不得不面对的头号问题。

  • 您的应用程序启动正常,但一两分钟后它开始降级
  • 确保您不会抓住您不期望的对象实例
  • 查找垃圾收集事件中存在的对象

答案 1 :(得分:1)

让我们做一些简单的事情。无论如何,您将需要下采样您的数据。在200hz-600hz的UI上显示数据通常会导致问题。

我的建议 - 以你可以使用的最长持续时间启动你选择的计时器。让我们从1000毫秒开始。

  1. 每次计时器到期时,请关闭所有计时器 财产变更事件
  2. 设备更新时不要引发属性更改事件
  3. 每秒一次,您的处理将更新,您的应用程序将保持响应。

答案 2 :(得分:-1)

这里讨论了很多好的信息,但就实施而言,这就是我的方式:

    private Thread PropertyChangedQueueThread;
    private List<string> ChangedPropertiesQueue = new List<string>();
    private ManualResetEvent PropertyChangedQueueBlocker = new ManualResetEvent(false);

    private void PropertyChangedQueueWorker()
    {
        while (!this.disposingValue)
        {
            PropertyChangedQueueBlocker.WaitOne();

            string PropName = ChangedPropertiesQueue.Last();
            ChangedPropertiesQueue.RemoveAll(i => i == PropName);
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropName));

            if (ChangedPropertiesQueue.Count() == 0)
                PropertyChangedQueueBlocker.Reset();
        }
    }

    internal void QueuePropertyChangedEvent(string PropertyName)
    {
        ChangedPropertiesQueue.Add(PropertyName);
        PropertyChangedQueueBlocker.Set();
    }

似乎工作得非常好并且它似乎可以自我扩展,因此无论有多少属性更改,只有最新的内容才会显示在UI中。除非我想象性能提升,否则它似乎能够满足我的需求。

我不太确定使用List和.RemoveAll会导致什么样的性能损失,因为每次调用RemoveAll都会重新索引列表,因为我理解它。也许在可以是字符串的键上索引的类型更适合,例如Dictionary