我有System.Collections.Generic.SynchronizedCollection共享集合。我们的代码使用.Net 4.0任务库来跨越线程并将同步的集合传递给线程。到目前为止,线程还没有在集合中添加或删除项目。但是需要其中一个线程的新需求必须从集合中删除项目,而另一个线程只读取集合。在从集合中删除项目之前是否需要添加锁定?如果是这样,读者线程会是线程安全吗?或者建议获得线程安全的最佳方法?
答案 0 :(得分:4)
不,它不是完全线程安全的。在一个简单的控制台应用程序中尝试以下操作,看看它是如何崩溃的例外:
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();
注意,如果使用ConcurrentBag替换SynchronizedCollection,您将获得线程安全性:
var collection = new ConcurrentBag<int>();
SynchronizedCollection在此应用程序中根本不是线程安全的。改为使用Concurrent Collections。
答案 1 :(得分: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
类。
答案 2 :(得分:2)
是的,SynchronizedCollection会为你做锁定。
如果您有多个读者而只有一个编写器,您可能需要查看使用ReaderWriterLock而不是SynchronizedCollection。
另外,如果您是.Net 4+,请查看System.Collections.Concurrent
。这些类的性能比SynchronizedCollection好得多。