实时更新UI

我必须创建一个WPF UI,它包含实时Fx速率(货币+速率)更新并在网格中显示它们(大约每秒1000次更新,这意味着网格中的每一行每秒最多可更新1000次) 。网格在任何时间点都至少有50行。

为此,我创建了一个Viewmodel,它订阅更新事件,并将这些更新存储在并发字典中,其中key作为符号,值作为RateViewModel对象。 然后我有另一个可观察的集合,它包含所有那些rateviewmodel对象,并将其绑定到网格。

码:

public class MyViewModel { private readonly IRatesService ratesService; private readonly ConcurrentDictionary rateDictionary; private object _locker = new object(); public MyViewModel(IRatesService ratesService) { this.ratesService = ratesService; this.ratesService.OnUpdate += OnUpdate; rateDictionary = new ConcurrentDictionary(); RateViewModels = new ObservableCollection(); } 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 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; } } 

我有4个问题:

  • 有没有办法可以消除ObservableCollection,因为它导致2个不同的数据结构存储相同的项目 – 但仍然将我的更新中继到UI?

  • 我使用了Concurrent Dictionary,它导致锁定整个更新操作。 有没有其他聪明的方法来处理这个而不是锁定整个dicitionary或任何数据结构?

  • 我的UpdateRate方法也会锁定 – 我的RateviewModel上的所有属性都是只读的,除了价格,因为它正在更新。 有没有办法使这个primefaces,请注意价格是双倍的。

  • 有没有办法可以优化SearchAndUpdate方法,这与1st相关。 目前我认为这是O(n)操作。

使用.NET 4.0并省略了INPC以简化。

* 编辑: *你可以帮助我以更好的方式重写这一点,考虑所有4点吗? Psuedocode会这样做。

谢谢,-Mike

1)我不会担心周围有50个额外的参考

2)是的,无锁数据结构是可行的。 联锁是你的朋友在这里,他们几乎都是一个人。 如果您不经常更改字典中的项目, ReaderWriterLock是另一个不错的选择。

3)通常,如果您处理的数据量超过UI可以处理的数据,那么您将需要在后台进行更新,只需在UI线程上触发INPC,更重要的是可以放弃UI更新(同时仍然更新支持字段)。 基本方法将是这样的:

  1. 在支持字段上执行Interlocked.Exchange
  2. 使用Interlocked.CompareExchange将私有字段设置为1,如果这返回1退出,则仍有待处理的UI更新
  3. 如果Interlocked.CompareExchange返回0,则调用UI并触发属性更改事件并将限制字段更新为0(从技术上讲,如果您关心非x86,还需要做更多事情)

4) SearchAndUpdate似乎是多余的… UpdateRate应该冒泡到UI,如果你需要在observable集合中添加或删除项目,你只需要调用UI线程。

更新:这是一个示例实现…事情稍微复杂一点,因为你使用的是在32位CPU上没有免费获得primefaces性的双打。

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

迈克 –

我会稍微改变一下。 除非添加新的Fx行,否则你真的不需要Observable Collection。 如您所知,Observable Collection仅为您提供该场景中的内置更改通知。 如果你有一个50行的列表(例如)和Fx对象(代表每个单独的行)每秒更新1000次 – 那么你可以很好地使用对象上的Fx属性上的INotifyPropertyChanged并让该机制更新用户界面改变了。 我的想法是 – 这是一种更简单的UI更新方法,而不是将它们从一个集合移动到另一个集合

现在关于你的第二点 – 一秒内1000次更新(对于一个现有的FX对象) – 从技术上讲,从UI的角度看是不可读的 – 我采取的方法是冻结和解冻 – 这意味着你基本上拦截了InotifyPropertyChanged(因为它的触发UI并保持基于频率 – 例如 – 每1秒 – 无论我的所有FX对象的状态如何(刷新UI)。 现在在第二秒内 – 无论FX属性发生什么更新 – 它们都会自行覆盖 – 并且当1秒间隔发生时,最新/正确的值会显示在UI上。 这样一来 – 当UI显示到UI时,向UI显示的数据总是正确且相关的。

有几个因素需要考虑,特别是如果显示的费率数量会动态变化。 我假设1000个更新/秒来自UI线程以外的线程。

首先,您需要对UI线程的更新进行编组 – 为您完成现有ViewModel的更新,而不是为新的/已删除的ViewModel完成。 通过一秒1000次更新,您可能希望控制编组到UI线程的粒度以及这需要的上下文切换。 Ian Griffiths写了一篇关于此的精彩博客系列 。

第二,如果您希望UI保持响应,您可能希望避免尽可能多的第2代垃圾收集,这意味着最大限度地减少GC的压力。 在为每次更新创建新的Rate对象更新时,这可能是您的问题。

一旦你开始有几个屏幕做同样的事情,你会想找到一种方法将这种更新行为抽象为一个共同的组件。 另外,你会在ViewModel中散布线程代码,这很容易出错。

我创建了一个开源项目ReactiveTables ,它解决了这三个问题,并添加了一些其他function,例如能够过滤,排序,加入模型集合。 此外,还有演示如何将其与虚拟网格一起使用以获得最佳性能。 也许这可以帮助你/激励你。