2017-03-17 57 views
7

我有我已經通過屬性是.NET的ObservableCollection <> ToList()線程安全嗎?如果沒有,如何進行

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

綁定到我的GUI有展現原木其他地方的一個子集的需求日誌的一個ObservableCollection,所以我有:

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; 

     } 

在我添加「ToList()」之前,我偶爾偶爾會遇到有關「集合被修改;枚舉操作可能不會執行」的異常「有道理 - 有人可以在我迭代時添加到日誌。我從Collection was modified; enumeration operation may not execute得到了「ToList」的想法,這似乎表明ToList是要走的路,並暗示它是線程安全的。但是ToList()線程安全嗎?我假設內部它必須使用列表並迭代它?如果有人在同一時間添加到該列表中呢?僅僅因爲我沒有看到問題並不意味着沒有問題。

我的問題。 ToList()線程安全,如果不是,保護日誌的最佳模式是什麼?如果ToList()是線程安全的,那麼你有參考嗎?

獎金問題。如果需求發生變化,並且我需要在GUI上顯示的所有內容都是LogsForDisplay和Not Logs,那麼是否可以將Logs更改爲可以解決問題的其他內容?如ImmutableList?然後,我不必打電話給ToList <>,我認爲需要一些時間才能製作副本。

讓我知道我是否可以提供澄清。 謝謝,

戴夫

+2

[documentation](https://msdn.microsoft.com/en-us/library/ms668604%28v=vs.110%29.aspx)不保證任何有用的方法或屬性是線程安全的,因此你必須假設他們都不是。您可以在日誌記錄方法(您正在添加到「日誌」集合中)和「ToList」周圍尋找'lock'。這似乎只能解決這個問題。可能覆蓋派生於ObservableCollection的類中的所有插入等方法,以使用['SyncRoot']進行鎖定(https://msdn.microsoft.com/zh-cn/library/bb353794(v = vs.110).aspx )財產。 – slawekwin

+0

所有好的答案,我感謝大家!只能遺憾地選擇1個。 – Dave

回答

5

ToList擴展方法的實現通過Array.Copy方法,其中,當你隱藏Collection was modified錯誤不是線程安全的,你可能會面臨怪異歸結爲從一個陣列複製到另一個項目在Array.Copy calll期間底層商品發生更改時的行爲。

我建議它使用CollectionView進行綁定,我在相似案例中使用它已經很長時間了,到目前爲止還沒有遇到任何問題。

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

你可以有另一個CollectionView針對不同的使用情況,如:

// 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擴展方法是 「線程安全的」 當滿足以下兩個條件都滿足:

  • Logs集合僅使用Add方法修改。也就是說,只有將項目添加到集合的末尾。項目不會被刪除或插入。這保證迭代項目是安全的。
  • 滿足以下兩個條件之一:
    • 現有項目從不修改。
    • 現有項目可能會被修改,但在LogsForDisplay.get方法中不需要集合中所有項目的最新(一致)狀態。

如果這些條件不滿足,你必須要麼使用ImmutableListObservableCollection或使用鎖底層集合。

如果滿足這兩個條件,則不必使用foreach並使用ToList<TSource>複製集合,那麼可以安全地使用帶有索引的for循環。

1

正如alex.b表示,.ToList方法不是線程安全的,這鏈接到源代碼證明http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,d2ac2c19c9cf1d44

您有哪些選擇?那麼,你有很多:

  1. 如果你想留下來與的ObservableCollection和100%安全的,那麼你有一個鎖statement.Of過程來包裝Logs.ToList()在你需要總結的話鎖定任何修改日誌集合的進程。
  2. 我注意到,LogsForDisplay是一個只讀(?)屬性可能綁定到WPF網格。如果要僅按需顯示數據,而不是每次更改日誌集合,則可以使用線程安全集合(如Immutable集合或System.Collections.Concurrent命名空間的集合(如ConcurrentDictionary)輕鬆地替換日誌的類型。由於您想返回日誌的子集,因此無法避免將項目複製到另一個列表中並稍後返回。即使在這種情況下,在調用.ToList()擴展名時仍然必須使用Lock,但只能使用一次。
1

ToList不是線程安全的,不可能是線程安全的,因爲它是一個擴展方法。這意味着它只能在一些擴展方法中提供線程安全性,這些擴展方法都會使用一些同步,但不能保護集合上的直接線程不安全調用。見執行here(如已經參考)。

但是你爲什麼要談論線程安全呢? ObservableCollection本身並不是線程安全的,所以它很奇怪,你說某些併發操作可能是原始錯誤的來源。因此,如果您正確使用Logs集合,則根本不必使用ToList。

1

簡單的解決方案是實現自己的線程安全的ObservableCollection,觀察集合的 一個簡單的線程友好的版本:

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方法throws an exception當你開始在forreach循環枚舉它後集合被修改。方法ToList將複製ObservableCollection的內部數組,因爲源集合實現ICollection<T>接口,並且不會拋出此類異常,因爲無法更改該數組的大小。如果同時修改ObservableCollection,則不會得到這些更改。但是你要返回的數據無論如何都是陳舊的。所以這是安全的方法,在你的情況下足夠好。

鏈接,這樣就可以檢查並確保自己:Enumerable.csList.cs

解決方案由@ alex.b提供也將工作做好,如果它符合您的要求。

對於此任務,您不需要任何線程安全集合,因爲它們只會增加額外的同步開銷。

相關問題