以下代码解释了我的问题。 我知道列表不是线程安全的。但是这个潜在的“真正”原因是什么?
class Program
{
static void Main(string[] args)
{
List<string> strCol = new List<string>();
for (int i = 0; i < 10; i++)
{
int id = i;
Task.Factory.StartNew(() =>
{
AddElements(strCol);
}).ContinueWith((t) => { WriteCount(strCol, id.ToString()); });
}
Console.ReadLine();
}
private static void WriteCount(List<string> strCol, string id)
{
Console.WriteLine(string.Format("Task {0} is done. Count: {1}. Thread ID: {2}", id, strCol.Count, Thread.CurrentThread.ManagedThreadId));
}
private static void AddElements(List<string> strCol)
{
for (int i = 0; i < 20000; i++)
{
strCol.Add(i.ToString());
}
}
}
答案 0 :(得分:19)
我将跳过明显的答案“列表不是线程安全的” - 你已经知道了。
列表项目保存在内部数组中。将项目添加到List时,至少有两个阶段(从逻辑的角度来看)。首先,List获取一个索引,指示新项目的放置位置。它使用此索引将新项目放入数组中。然后它递增索引,这是第二阶段。如果第二个(或第三个,第四个......)线程同时添加新项目,则可能会有两个(3,4,...)新项目放入索引之前的同一个数组位置由第一个线程递增。项目被覆盖并丢失。
添加新项目和递增索引的内部操作必须始终一次完成,以使列表成为线程安全的。那就是所谓的关键部分。这可以通过锁来实现。
希望这能解释一下。
答案 1 :(得分:13)
这是因为List<T>
不是线程安全的。
您应该为此使用线程安全集合,例如System.Collections.Concurrent中的一个集合。否则,您将需要同步对List<T>
的所有访问(即:将每个Add
调用放入锁中),这将完全无法使用多个线程调用此目的,因为您是在这种情况下没有做任何其他工作。