2013-10-01 30 views
7

Muszę utworzyć interfejs użytkownika WPF, który subskrybuje aktualizacje w czasie rzeczywistym Fx Rate (Currency + rate) i wyświetla je w siatce (około 1000 aktualizacji na sekundę, co oznacza, że ​​każdy wiersz w tabeli może uzyskać zaktualizowano upto 1000 razy na sekundę). Siatka miała co najmniej 50 rzędów w dowolnym momencie.Aktualizowanie interfejsu użytkownika w czasie rzeczywistym

W tym celu utworzyłem Viewmodel, który subskrybuje zdarzenia aktualizacji i zapisuje te aktualizacje w współbieżnym słowniku z kluczem jako symbolem i wartością jako obiekt RateViewModel. Następnie mam kolejną obserwowalną kolekcję, która ma wszystkie obiekty rateviewmodel i wiąże ją z siatką.

Kod:

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

Mam 4 pytania o to:

  • Czy istnieje sposób mogę wyeliminować ObservableCollection, ponieważ to prowadzi do 2 różnych datastructures przechowujących te same elementy - ale nadal moje aktualizacje są przekazywane do interfejsu użytkownika?

  • Użyłem słownika równoczesnego, który prowadzi do zablokowania całej operacji aktualizacji. Czy istnieje jakaś inna sprytna metoda radzenia sobie z tym, zamiast blokowania całej bazy danych lub jakiejkolwiek bazy danych?

  • Moja metoda UpdateRate również blokuje się - wszystkie moje właściwości na moim RateviewModel są tylko do odczytu, z wyjątkiem ceny, ponieważ jest ona aktualizowana. Czy istnieje sposób na uczynienie tego atomowego, proszę zauważyć, że cena przychodzi jako podwójna.

  • Czy istnieje sposób, aby zoptymalizować metodę SearchAndUpdate, jest to związane z 1st. W tej chwili uważam, że jest to operacja O (n).

Korzystanie .NET 4.0 i pominięto INPC dla zwięzłości.

* EDYCJA: * Czy mógłbyś mi pomóc w lepszym przepisaniu tego, biorąc pod uwagę wszystkie 4 punkty? Psuedocode to zrobi.

Dzięki, -Mike

Odpowiedz

4

1) I nie martwić się o 50 dodatkowych bibl pływających wokół

2) Tak, struktury danych lockless są wykonalne. Interlocked Jest tu twój przyjaciel i są prawie wszyscy offs. ReaderWriterLock to kolejna dobra opcja, jeśli nie zmieniasz często pozycji w słowniku.

3) Ogólnie rzecz biorąc, jeśli masz do czynienia z większą ilością danych, więcej danych niż interfejs użytkownika może obsłużyć, będziesz chciał zrobić aktualizacje w tle, tylko uruchamiaj INPC w wątku UI, a co ważniejsze, umieść upuść aktualizacje interfejsu użytkownika (wciąż aktualizując pole kopii).Podstawowe podejście będzie mniej więcej tak:

  1. Czy to Interlocked.Exchange na polu podkładowej
  2. Korzystanie Interlocked.CompareExchange ustawić prywatne pole do 1, jeśli ta zwraca 1 wyjście ponieważ posiadał wciąż jest w toku aktualizacji UI
  3. Jeśli Interlocked.CompareExchange zwrócone 0, powołać do interfejsu użytkownika i ogień właściwości zmieniono zdarzenie i aktualizuje Ci dławiący pole do 0 (technicznie jest więcej trzeba zrobić, jeśli zależy Ci na nie x86)

4) SearchAndUpdate Wydaje superf luous ... UpdateRate powinien być przepełniony interfejsem użytkownika i musisz tylko wywołać wątek interfejsu użytkownika, jeśli chcesz dodać lub usunąć element do obserwowalnej kolekcji.

Aktualizacja: tutaj jest przykładowa implementacja ... rzeczy są trochę bardziej skomplikowane, ponieważ używasz podwójnych układów, które nie pobierają atomowości za darmo na 32-bitowych procesorach.

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

Czy możesz umieścić jakiś pseudo kod do punktu 3, proszę? – Mike

+0

Nie jestem do końca jasny o tym "Użyj Interlocked.CompareExchange, aby ustawić prywatne pole na 1, jeśli to zwróci 1 wyjście, ponieważ wciąż jest oczekująca aktualizacja interfejsu użytkownika Jeśli Interlocked.CompareExchange zwróciło 0, wywołaj interfejs użytkownika i zwolnij właściwość zmieniła wydarzenie i zaktualizuje pole dławienia do 0 (technicznie jest więcej, co musisz zrobić, jeśli zależy ci na x86) "--- Czy możesz zamieścić krótki fragment kodu? – Mike

+0

@Mike Dodałem kod pokazujący technikę – Yaur

3

Mike -

Chciałbym podejść do tego trochę inaczej. Naprawdę nie potrzebujesz Kolekcji obserwowalnej, chyba że dodawane są nowe wiersze Fx. Observable Collection, jak wiesz, daje tylko wbudowane powiadomienie o zmianie w tym scenariuszu. Jeśli masz listę 50 wierszy (na przykład) i obiekt Fx (który reprezentuje każdy pojedynczy wiersz) jest aktualizowany 1000 razy na sekundę - wtedy możesz bardzo dobrze użyć INotifyPropertyChanged na właściwościach Fx w obiekcie i pozwolić na aktualizację mechanizmu interfejs użytkownika, gdy się zmieniają. Moja linia myślenia polega na tym, że jest to prostsze podejście do aktualizacji interfejsu użytkownika niż przenoszenie ich z jednej kolekcji do drugiej. Teraz w odniesieniu do drugiego punktu - 1000 aktualizacji w sekundę (do istniejącego obiektu FX) - co technicznie jest nieczytelne z punktu widzenia interfejsu użytkownika - podejście, które podjąłem to zamrożenie i odwilŜenie - co oznacza, Ŝe zasadniczo przechwytywacie InotifyPropertyChanged (jako jego wypalanie do UI) i utrzymuję ją w oparciu o częstotliwości - na przykład - co 1 sekundę - bez względu na mój status wszystkie obiekty FX to (odśwież interfejs użytkownika). Teraz w ciągu tej sekundy - niezależnie od tego, co dzieje się z właściwościami FX - zachowują one nadpisanie - a najnowsza/poprawna wartość, gdy 1 sekundowy interwał się dzieje - jest pokazywana UI. W ten sposób dane wyświetlane w interfejsie użytkownika są zawsze poprawne i istotne, gdy są wyświetlane w interfejsie użytkownika.

+0

Wybierając interwał aktualizacji, należy pamiętać, że użytkownik może sortować według często aktualizowanej kolumny, w którym to przypadku liczby będą się zmieniać nie tylko, ale również będą zmieniały się pozycje (trudniej będzie wizualnie śledzić wartości). Myślę, że Patrick nie żyje z odroczonymi aktualizacjami INPC, a Ty * możesz * być w stanie dalej optymalizować, zwijając powiadomienia w różnych właściwościach (podnieście pojedyncze zdarzenie PropertyChanged z "null" dla nazwy właściwości). Większość sieci poprawnie obsługuje to, odświeżając cały wiersz, a niektóre siatki (jak DevExpress) zrobiłyby to dla każdej zmiany właściwości w każdym razie. –

0

Jest kilka czynników, które należy wziąć pod uwagę, szczególnie jeśli liczba wyświetlanych stawek zmieni się dynamicznie. Zakładam, że 1000 aktualizacji/s pochodzi z wątku innego niż wątek interfejsu użytkownika.

Po pierwsze, należy przeprowadzić aktualizacje wątku interfejsu użytkownika - zrobić dla ciebie aktualizacje istniejącego ViewModel, nie zrobione dla ciebie dla nowych/usuniętych ViewModels. Przy 1000 aktualizacjach na sekundę prawdopodobnie chcesz kontrolować szczegółowość sortowania do wątku UI i przełączania kontekstów, które to powoduje. Ian Griffiths napisał świetną blog series na ten temat.

Po drugie, jeśli chcesz, aby Twój interfejs działał prawidłowo, prawdopodobnie chcesz uniknąć jak największej liczby zbiórek śmieci z gen 2, co oznacza zminimalizowanie nacisku na GC. Może to być problem w twoim przypadku, ponieważ tworzysz nową aktualizację obiektu Rate dla każdej aktualizacji.

Po uruchomieniu kilku ekranów, które robią to samo, należy znaleźć sposób na zsumowanie tego zachowania aktualizacji na wspólny składnik. Innymi słowy będziesz zraszanie wątku kodu za pośrednictwem ViewModels, który jest podatny na błędy.

Stworzyłem projekt open source, ReactiveTables, który rozwiązuje te trzy problemy i dodaje kilka innych funkcji, takich jak możliwość filtrowania, sortowania i dołączania do kolekcji modeli. Są też dema pokazujące, jak używać go z wirtualnymi sieciami, aby uzyskać najlepszą wydajność. Może to może ci pomóc/zainspirować.

Powiązane problemy