2012-11-25 53 views
1

我有System.Collections.Generic.SynchronizedCollection共享集合。我们的代码使用.Net 4.0任务库跨越线程并将同步的集合传递给线程。到目前为止,线程并没有向集合中添加或删除项目。但是需要其中一个线程的新需求必须从集合中删除项目,而另一个线程只读取集合。在从集合中删除项目之前是否需要添加锁定?如果是这样,读者线程是否是线程安全的?或建议最好的方式来获得线程安全?同步收集线程安全

+0

如果您使用的是SynchronizedCollection,它应该已经被提供在该添加/从集合中删除操作的适当的锁定。 – Joe

+0

这是否也支持在从另一个线程中删除项目时枚举另一个线程中的集合?我是否需要锁定枚举部分? – Amzath

+0

现有的“SynchronizedCollection”的全部重点是为所有操作提供线程安全的集合(它通过锁定所有操作)。如果您使用的是ICollection ,那么您可能需要自行锁定。请注意,如果您的目标是4.0框架或更高版本,则可能会看到使用System.Collections.Concurrent时的性能改进...... – Joe

回答

2

是,SynchronizedCollection会做锁定你。

如果您有多个读者和只有一个作家,你可能想看看使用ReaderWriterLock,而不是SynchronizedCollection。

另外,如果你是净4+然后看看System.Collections.Concurrent。这些类比SynchronizedCollection有更好的性能。

+1

同步收集对于多个阅读器和一个作者是否是线程安全的? – Amzath

+0

是的,但效率不高 –

+0

我认为这取决于使用集合的情况。例如:如果您尝试通过特定值(例如,一个属性值大于20的项目),并更改该项目,您没有原子操作,但需要锁定 – Offler

3

不,它不完全是线程安全的。尝试在一个简单的控制台的应用程序下面,看看它是如何与一个异常崩溃:

var collection = new SynchronizedCollection<int>(); 

var n = 0; 

Task.Run(
    () => 
     { 
      while (true) 
      { 
       collection.Add(n++); 
       Thread.Sleep(5); 
      } 
     }); 

Task.Run(
    () => 
     { 
      while (true) 
      { 
       Console.WriteLine("Elements in collection: " + collection.Count); 

       var x = 0; 
       if (collection.Count % 100 == 0) 
       { 
        foreach (var i in collection) 
        { 
         Console.WriteLine("They are: " + i); 
         x++; 
         if (x == 100) 
         { 
          break; 
         } 

        } 
       } 
      } 
     }); 

Console.ReadKey(); 

enter image description here

请注意,如果您有ConcurrentBag更换SynchronizedCollection,你会得到线程安全:

var collection = new ConcurrentBag<int>(); 

SynchronizedCollection在此应用程序中根本不是线程安全的。改用Concurrent Collections。

4

亚历山大已经指出SynchronizedCollection对于这种情况不是线程安全的。 SynchronizedCollection实际上包装了一个普通的通用列表,并且只是将每个调用委托给基础列表,并且围绕该调用发出锁。这也在GetEnumerator中完成。因此,枚举器的获取是同步的,但不是实际的枚举。

var collection = new SynchronizedCollection<string>(); 
collection.Add("Test1"); 
collection.Add("Test2"); 
collection.Add("Test3"); 
collection.Add("Test4"); 

var enumerator = collection.GetEnumerator(); 
enumerator.MoveNext(); 
collection.Add("Test5"); 
//The next call will throw a InvalidOperationException ("Collection was modified") 
enumerator.MoveNext(); 

当使用foreach时,将以这种方式调用枚举器。因此,在通过此数组枚举之前添加ToArray()将不起作用,因为这将首先枚举到此数组中。 当你在你的foreach中做什么时,这个枚举可能会更快,所以它可以减少获得并发问题的可能性。正如理查德指出的那样:为了真正的线程安全,请去System.Collections.Concurrent类。

+0

然而,在调用'ToArray'之前,您可以使用集合的'SyncRoot'属性。 –