C# 多个线程将元素添加到一个列表中。为什么列表中的项目总是比预期的少?

C# 多个线程将元素添加到一个列表中。为什么列表中的项目总是比预期的少?,c#,multithreading,concurrency,parallel-processing,task-parallel-library,C#,Multithreading,Concurrency,Parallel Processing,Task Parallel Library,下面的代码解释了我的问题。 我知道列表不是线程安全的。但这背后的“真正”原因是什么 class Program { static void Main(string[] args) { List<string> strCol = new List<string>(); for (int i = 0; i < 10; i++) { int id = i;

下面的代码解释了我的问题。 我知道列表不是线程安全的。但这背后的“真正”原因是什么

    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());
        }
    }
}
类程序
{
静态void Main(字符串[]参数)
{
List strCol=新列表();
对于(int i=0;i<10;i++)
{
int id=i;
Task.Factory.StartNew(()=>
{
附录(strCol);
}).ContinueWith((t)=>{WriteCount(strCol,id.ToString());});
}
Console.ReadLine();
}
私有静态void WriteCount(列表strCol,字符串id)
{
WriteLine(string.Format(“任务{0}已完成。计数:{1}。线程ID:{2}”,ID,strCol.Count,Thread.CurrentThread.ManagedThreadId));
}
专用静态无效加法器(列表strCol)
{
对于(int i=0;i<20000;i++)
{
strCol.Add(i.ToString());
}
}
}

这是因为
列表
不是线程安全的


您应该为此使用线程安全的集合,例如中的一个集合。否则,您需要同步对
列表的所有访问(即:将每个
Add
调用放在一个锁中),这将完全违背使用多个线程调用此函数的目的,因为在这种情况下您不会执行任何其他工作

我将跳过显而易见的答案“列表不是线程安全的”——这一点您已经知道了

列表项保存在内部数组中。将项目添加到列表中时,至少有两个阶段(从逻辑角度来看)。首先,List获取一个索引,指示将新项放置在何处。它使用此索引将新项放入数组。然后增加索引,这是第二阶段。如果第二个(或第三个,第四个,…)线程同时添加新项,则在索引由第一个线程递增之前,可能会有两个(3,4,…)新项放入同一数组位置。项目被覆盖并丢失

添加新项和增加索引的内部操作必须始终一次性完成,列表才是线程安全的。这就是所谓的关键部分。这可以通过锁来实现


希望这能解释一点。

如问题所述。我知道列表不是线程安全的。但这背后的“真实”原因是什么?@崔鹏飞“真实”原因可能是,假设开发人员不是一次从多个线程与
列表
交互,有些操作(数据拷贝、数组大小调整等)是在不锁定的情况下完成的。使
列表
线程安全所需的额外同步将显著影响性能,因此BCL设计人员选择省略它,并记录线程安全性的不足,以便如果您需要线程安全访问,您可以围绕列表构建它。与Java的
ArrayList
和许多其他
List
类相同。@崔鹏飞:因为添加到
List
的操作不是完全原子的,不是线程安全的。一个线程开始添加一个元素,另一个线程通过添加自己的元素使其短路。@调用List.add时,它只是检查底层数组中要写入的元素。。。这有点像“获取当前大小”、“将[size]的值设置为新值”、“设置当前大小”。如果另一个线程在第一个线程的“Set”出现时启动,它们都将写入同一个元素,并将总大小设置为相同,并且一个值将从结果中“消失”。看起来有些线程正在相互写入。内部列表必须使用计数器递增到下一个位置,并且由于它不是线程安全的,所以它的覆盖值