7

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

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

代码:

public class MyViewModel
    {
        private readonly IRatesService ratesService;

        private readonly ConcurrentDictionary<string, RateViewModel> rateDictionary;
        private object _locker = new object();

        public MyViewModel(IRatesService ratesService)
        {
            this.ratesService = ratesService;
            this.ratesService.OnUpdate += OnUpdate;
            rateDictionary = new ConcurrentDictionary<string, RateViewModel>();
            RateViewModels = new ObservableCollection<RateViewModel>();            
        }

        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<RateViewModel> 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?

  • 我使用了并发字典,这会导致锁定整个更新操作。有没有其他聪明的方法来处理这个问题,而不是锁定整个字典或任何数据结构?

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

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

使用.NET 4.0并为了简洁省略了 INPC。

*编辑: *你能帮我以更好的方式重写这个,考虑到所有 4 点吗?伪代码会做。

谢谢,-迈克

4

3 回答 3

4

1) 我不会担心会有 50 个额外的裁判四处飘荡

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

(同时仍在更新支持字段)。基本方法将类似于:

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

4)SearchAndUpdate似乎是多余的......UpdateRate应该冒泡到 UI,如果您需要向可观察集合添加或删除项目,您只需要调用 UI 线程。

更新:这里是一个示例实现...事情稍微复杂一些,因为您使用的双精度数在 32 位 CPU 上无法免费获得原子性。

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


}    
于 2013-10-01T23:49:46.020 回答
3

迈克-

我会以不同的方式处理这个问题。除非添加了新的 Fx 行,否则你真的不需要 Observable Collection。如您所知,Observable Collection 仅在该场景中为您提供内置更改通知。如果您有一个包含 50 行的列表(例如)并且 Fx 对象(代表每个单独的行)每秒更新 1000 次 - 那么您可以很好地在对象的 Fx 属性上使用 INotifyPropertyChanged 并让该机制更新UI 变化。我的思路是——这是一种更简单的 UI 更新方法,而不是将它们从一个集合移动到另一个集合

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

于 2013-10-02T00:51:21.127 回答
0

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

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

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

一旦您开始拥有几个执行相同操作的屏幕,您就会想找到一种方法将这种更新行为抽象到一个通用组件中。否则,您将通过容易出错的 ViewModel 散布线程代码。

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

于 2014-03-10T07:42:13.953 回答