2017-03-17 21 views
7

Mam ObservableCollection kłód, że mam związany z moim GUI poprzez właściwośćCzy bezpieczny jest .NET ObservableCollection <> ToList()? Jeśli nie, jak postępować

public ObservableCollection<ILog> Logs {get; private set;} 

Jest to wymóg, aby pokazać podzbiór dzienników gdzieś indziej, więc mam:

public ObservableCollection<ILog> LogsForDisplay 
    { 
     get 
     { 
      ObservableCollection<ILog> displayLogs = new ObservableCollection<ILog>(); 
      foreach (Log log in Logs.ToList()) // notice the ToList() 
      { 
       if (log.Date != DateTime.Now.Day) 
        continue; 
       displayLogs.Add(log); 
      } 
      return displayLogs; 

     } 

Przed I dodaje „ToList()” mam wyjątki od czasu do czasu o „Kolekcja została zmodyfikowana; operacja wyliczania nie może wykonać” to ma sens - ktoś może dodać do rejestru, a ja jestem iteracji nad nim. Wpadłem na pomysł "ToList" z Collection was modified; enumeration operation may not execute, który wydaje się sugerować, że ToList to sposób na udanie się i sugeruje, że jest bezpieczny dla wątków. Ale czy wątek ToList() jest bezpieczny? Zakładam, że wewnętrznie musi korzystać z listy i iterować nad nią? A co, jeśli ktoś doda do tej listy w tym samym czasie? To, że nie widziałem problemu, nie oznacza, że ​​go nie ma.

Moje pytanie. Czy wątek ToList() jest bezpieczny, a jeśli nie, jaki jest najlepszy wzór do ochrony dzienników? Jeśli funkcja ToList() jest bezpieczna dla wątków, czy masz referencję?

Dodatkowe pytanie. Jeśli wymagania zmieniły się i wszystko, co musiałem wyświetlić w GUI, to LogsForDisplay i NOT Logs, czy mogę zmienić Logi na coś innego, co rozwiąże problem? Takich jak ImmutableList? Wtedy nie musiałbym wywoływać ToList <> której założenie zajmuje trochę czasu, aby wykonać kopię.

Daj mi znać, jeśli mogę udzielić wyjaśnień. Dzięki,

Dave

+2

[Dokumentacja] (https://msdn.microsoft.com/en-us/library/ms668604%28v=vs.110%29.aspx) nie gwarantuje żadnej użytecznej metody ani właściwości do zabezpieczenia wątków, w związku z tym należy założyć, że wszyscy nie są. Możesz przejść do metody 'lock' in you logowania (gdzie dodajesz do kolekcji' Logs') i dookoła 'ToList'. Wydaje się, że jest to tylko pewien sposób na rozwiązanie tego problemu. Prawdopodobnie zastąp wszystkie metody wstawiania itd. W klasie pochodzące z 'ObservableCollection', aby zablokować za pomocą [' SyncRoot'] (https://msdn.microsoft.com/en-us/library/bb353794 (v = vs.110) .aspx) własność. – slawekwin

+0

Wszystkie dobre odpowiedzi i dziękuję wszystkim! Może tylko wybrać 1 niestety. – Dave

Odpowiedz

5

Realizacja ToList metodę rozszerzenia sprowadza się do kopiowania elementów z jednej tablicy do drugiej poprzez Array.Copy metody, nie który podczas ukrywania Collection was modified błąd z was jest bezpieczny wątku i może napotkać dziwne zachowanie, gdy podstawowe elementy są zmieniane podczas wywołania Array.Copy.

Co proponuję użyć do wiązania z użyciem CollectionView, używam go przez dość długi czas w podobnych przypadkach i nie napotkałem żadnych problemów.

// somewhere in .ctor or other init-code 
var logsForDisplay = new CollectionView(this.Logs); 
logsForDisplay.Predicate = log => ((Log)log).Date == DateTime.Now.Day; 

public CollectionView LogsForDisplay { get { return this.logsForDisplay; } } 

Możesz mieć inny CollectionView dla różnych przypadków użycia, np:

// somewhere in .ctor or other init-code 
var yesterdaysLogs = new CollectionView(this.Logs); 
yesterdaysLogs.Predicate = log => ((Log)log).Date == DateTime.Now.AddDays(-1).Day; 

public CollectionView YesterdaysLogs{ get { return this.yesterdaysLogs; } } 
1

ToList metoda rozszerzenie jest "wątku bezpieczny", gdy dwa następujące warunki są spełnione:

  • Kolekcja Logs jest modyfikowana tylko za pomocą metody Add. To znaczy, tylko dodając elementy na końcu kolekcji. Przedmioty nigdy nie są usuwane ani wstawiane. Gwarantuje to bezpieczeństwo iteracji przedmiotów.
  • Jeden z dwóch poniższych warunków:
    • Istniejące elementy nigdy nie są modyfikowane.
    • Istniejące elementy mogą być modyfikowane, ale najnowszy (spójny) stan wszystkich elementów w kolekcji nie jest wymagany w metodzie LogsForDisplay.get.

Jeżeli te warunki nie są spełnione, będziesz musiał użyć ImmutableList jako podstawowej kolekcji ObservableCollection lub użyć zamków.

Jeśli te dwa warunki są spełnione, nie trzeba używać foreach i tworzyć kopii kolekcji przy użyciu ToList<TSource>, można bezpiecznie używać pętli for z indeksowaniem.

1

Jak alex.b powiedział, metoda .ToList nie jest wątku bezpieczne i link do kodu źródłowego dowodzi, że http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,d2ac2c19c9cf1d44

Jakie są opcje? Cóż, trzeba wiele:

  1. Jeśli chcesz trzymać się z ObservableCollection i być w 100% bezpieczne potem trzeba owinąć Logs.ToList() z zamkiem statement.Of oczywiście w takim przypadku należy owinąć z blokadą dowolnego procesu, który modyfikuje kolekcję Logs.
  2. Zauważyłem, że LogsForDisplay jest właściwością readonly (?), Która może być powiązana z siatką WPF. Jeśli chcesz wyświetlać dane tylko na żądanie i nie za każdym razem, gdy zmienia się kolekcja Log, możesz łatwo zastąpić typ Logs z kolekcją wątków bezpiecznych, taką jak kolekcja Immutable lub kolekcja z przestrzeni nazw System.Collections.Concurrent, jak ConcurrentDictionary . Ponieważ chcesz zwrócić podzbiór Logów, nie można uniknąć kopiowania elementów do innej listy i zwrócić później. Nawet w tym przypadku nadal musisz używać blokady, gdy wywołasz rozszerzenie .ToList(), ale tylko raz.
1

The ToList nie jest bezpieczny dla wątków, a nieprawdopodobne jest, aby można było bezpiecznie stosować wątki, ponieważ jest to metoda rozszerzenia. Oznacza to, że może zapewnić bezpieczeństwo wątków tylko w ramach niektórych zestawów metod rozszerzania, które wszystkie wykorzystywałyby pewną synchronizację, ale które nie chroniłyby przed bezpośrednim wątkiem niebezpiecznych wywołań w kolekcji. Zobacz implementację here (jak już wspomniano).

Ale dlaczego w ogóle mówisz o bezpieczeństwie nici? ObservableCollection nie jest bezpieczna dla wątków, więc dziwne jest to, że niektóre współbieżne operacje mogą być źródłem oryginalnego błędu. Tak więc, jeśli prawidłowo korzystałeś z kolekcji Logs, nie musiałbyś w ogóle korzystać z ToList.

1

Prostym rozwiązaniem jest wdrożenie własnego wątku bezpieczne ObservableCollection, prosty gwint przyjazną wersję kolekcji obserwowalnym:

public class NEWObservableCollection<T> : ObservableCollection<T> 
    { 
     public override event NotifyCollectionChangedEventHandler CollectionChanged; 
     protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 
     { 
      NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged; 
      if (CollectionChanged != null) 
       foreach (NotifyCollectionChangedEventHandler notifyCollectionChangedEventHandler in CollectionChanged.GetInvocationList()) 
       { 
        DispatcherObject dispatcherObject = notifyCollectionChangedEventHandler.Target as DispatcherObject; 
        if (dispatcherObject != null) 
        { 
         Dispatcher dispatcher = dispatcherObject.Dispatcher; 
         if (dispatcher != null && !dispatcher.CheckAccess()) 
         { 
          dispatcher.BeginInvoke(
           (Action)(() => notifyCollectionChangedEventHandler.Invoke(this, 
            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))), 
           DispatcherPriority.DataBind); 
          continue; 
         } 
        } 
        notifyCollectionChangedEventHandler.Invoke(this, e); 
       } 
     } 
    } 
1

Enumerator.MoveNext metodę throws an exception gdy zbiór jest modyfikowany po rozpoczęciu wyliczając ją forreach pętli. Metoda ToList skopiuje wewnętrzną tablicę z ObservableCollection, ponieważ biblioteka źródłowa implementuje interfejs ICollection<T> i nie będzie generować takiego wyjątku, ponieważ nie można zmienić rozmiaru tablicy. Jeśli wersja ObservableCollection zostanie zmodyfikowana, w międzyczasie nie dostaniesz tych zmian. Ale dane, które zwrócisz, i tak są nieaktualne. Więc jest to bezpieczne podejście i wystarczająco dobre w twoim przypadku.

menu, dzięki czemu można sprawdzić i zapewnić sobie: Enumerable.cs i List.cs

Rozwiązanie dostarczone przez @ alex.b będzie również działać dobrze, jeśli spełnia określone wymagania.

Do wykonania tego zadania nie są potrzebne żadne kolekcje wątków, ponieważ dodadzą one dodatkowe obciążenie synchronizacji.

Powiązane problemy