实时更新UI

Mik*_*ike 7 c# wpf

我要创建一个WPF UI,这subsribes实时外汇汇率(货币+速度)的更新,并将其显示在网格(每秒大约1000次更新,这意味着网格中的每个行可能获得更新高达每秒1000次) .网格在任何时间点都至少有50行.

为此,我创建了一个Viewmodel,它订阅更新事件,并将这些更新存储在并发字典中,其中key作为符号,值作为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;
        }      
    }
Run Code Online (Sandbox Code Playgroud)

我有4个问题:

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

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

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

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

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

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

谢谢,-Mike

Yau*_*aur 4

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,并且如果您需要向可观察集合添加或删除项目,则只需要调用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 {};


}    
Run Code Online (Sandbox Code Playgroud)