2013-10-01 60 views
7

我必須創建一個WPF用戶界面,用於實時Fx Rate(貨幣+費率)更新並將其顯示在網格中(每秒大約1000次更新,這意味着網格中的每一行都可以獲得更新upto每秒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,因爲它導致了兩種不同的數據結構存儲相同的項目 - 但仍然有我的更新中繼到用戶界面?

  • 我已經使用了Concurrent Dictionary,它會導致鎖定整個更新操作。有沒有其他聰明的方式來處理這個問題,而不是鎖定整個dicitionary或任何數據結構?

  • 我的UpdateRate方法也鎖 - 我的RateviewModel上的所有屬性都是隻讀的,除了價格以外,因爲它正在更新。有沒有辦法使這個原子,請注意,價格是雙倍。

  • 有沒有一種方法可以優化SearchAndUpdate方法,這與第一種有關。目前我相信這是一個O(n)操作。

使用.NET 4.0和省略INPC爲簡潔。

* 編輯: *請您幫助我以更好的方式重寫這一點,並將所有4點考慮在內? Psuedocode會做。

感謝, -Mike

回答

4

1)我不會擔心50額外的裁判左右浮動

2)是的,無鎖數據結構是可行的。 Interlocked這裏是你的朋友,他們幾乎都是一樣的。 ReaderWriterLock是另一個不錯的選擇,如果你不經常改變你的字典中的東西。 3)一般來說,如果你處理的數據比UI可以處理的數據更多,你將要在後臺執行更新,只需在UI線程上觸發INPC,並且更重要的是有一個工具來放棄UI更新(同時仍然更新後臺字段)。基本做法是將是這樣的:

  1. 在支持字段
  2. 使用Interlocked.CompareExchange做一個Interlocked.Exchange設置私有字段設置爲1,如果監守仍有待處理的UI更新
  3. 這將返回1退出
  4. 如果Interlocked.CompareExchange返回0,調用到UI和解僱你的屬性更改事件,並更新你限制字段設置爲0(技術上還有更多你需要的,如果你關心非x86的做)

4)SearchAndUpdate似乎superf luous ... UpdateRate應該冒泡到用戶界面,你只需要調用UI線程,如果你需要添加或刪除一個項目的observable集合。

更新:這裏是一個簡單的實現......事情有點複雜,因爲你使用的是雙打不獲得原子免費在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 {}; 


}  
+0

你可以發佈一些點3的僞代碼嗎? – Mike

+0

我不是很清楚這個「使用Interlocked.CompareExchange設置專用字段爲1,如果這返回1退出,因爲仍然有一個未決的UI更新 如果Interlocked.CompareExchange返回0,調用到UI並激發您的屬性更改了事件並將您的限制字段更新爲0(技術上,如果您關心的不是x86,則需要執行更多操作)「---您可以發佈一個簡短的代碼段嗎? – Mike

+0

@Mike我添加了代碼來顯示技術 – Yaur

3

邁克 -

我將採取不同的處理這一點。除非添加新的Fx行,否則實際上不需要Observable集合。您所知道的Observable Collection只會在該場景中爲您提供內置更改通知。如果您有50行(例如)的列表,並且Fx對象(代表每個單獨的行)每秒更新1000次 - 那麼您可以很好地在對象的Fx Properties上使用INotifyPropertyChanged並讓該機制更新用戶界面隨着他們的變化我的思路是 - 這是更新UI更簡單的方法,而不是將它們從一個集合移動到另一個集合

現在關於您的第二點 - 1000秒更新(針對現有FX對象) - 技術上是從UI角度不可讀 - 我採取的方法是凍融 - 這意味着你基本上是攔截INotifyPropertyChanged的(因爲它的發射到UI),並保持它基於頻率的 - 因此,例如 - 每1秒 - 不管我的狀態所有FX對象都是(刷新UI)。現在在那秒 - 無論FX屬性發生什麼更新 - 它們自己保持覆蓋 - 當1秒間隔發生時,最新/正確的值 - 會顯示給UI。這樣 - 顯示給用戶界面的數據始終是正確和相關的。

+0

在選擇更新間隔時,請記住用戶可以通過經常更新的列進行排序,在這種情況下,不僅數字會發生變化,而且行也會更改位置(難以直觀地跟蹤值)。我認爲帕特里克已經無法使用延遲的INPC更新,並且*可能*能夠通過摺疊通過屬性的通知進行進一步優化(使用'null'爲屬性名稱引發單個PropertyChanged事件)。大多數網格通過刷新整個行來正確處理這個問題,而且某些網格(如DevExpress)會爲每個屬性更改都執行此操作。 –

0

有幾個因素要考慮,特別是如果顯示率的數量會動態變化。我假設1000更新/秒來自UI線程以外的線程。

首先,你需要馬歇爾更新到UI線程 - 爲你做了更新現有的視圖模型,你沒有做新的/刪除的ViewModels。每秒更新1000次,您可能想要控制到UI線程的編組粒度以及這需要的上下文切換。 Ian Griffiths在此寫了一個很棒的blog series

第二個是,如果你想你的用戶界面保持響應,你可能想避免儘可能多的第2代垃圾收集儘可能這意味着降低對GC的壓力。在您爲每個更新創建一個新的Rate對象更新時,這可能是一個問題。

一旦你開始有你想找到一種方法來抽象此更新動作出到一個共同分量的同一件事的幾個畫面。其他方面,你會通過你的ViewModels灑水線程代碼,這是容易出錯。

我創建了一個開源項目,ReactiveTables,它解決了這三個問題,並添加了一些其他功能,例如可以過濾,排序和加入模型集合。還有演示顯示如何使用虛擬網格來獲得最佳性能。也許這可以幫助你/激勵你。