迭代时C#ConcurrentBag内存消耗

时间:2015-07-27 20:21:59

标签: c# list memory foreach

如果我从发布代码开始会更容易:

static void Main(string[] args)
{
    List<double> testLst = new List<double>();
    for (int i = 0; i < 20000000; i++) { testLst.Add(i); }

我已经填充了一个包含20,000,000个元素的List。我在任务管理器中看到该进程正在使用~300MB。如果我使用foreach循环遍历列表:

    foreach (var a in testLst.Take(10)) 
    {
        Console.WriteLine(a);
    }
}

内存使用量没有增加(我在Console.WriteLine上放了一个断点,正如我所说,我正在使用任务管理器测量它)。现在,如果我用ConcurrentBag替换List:

static void Main(string[] args)
{
    ConcurrentBag<double> testCB = new ConcurrentBag<double>();
    for (int i = 0; i < 20000000; i++) { testCB.Add(i); }

    foreach (var a in testCB.Take(10)) 
    {
        Console.WriteLine(a);
    }
}
在foreach循环之前,内存使用量为450~500MB。问题是:为什么如果在foreach循环使用内部跳转到~900MB?

我希望ConcurrentBag与List相比消耗更多内存,但我不明白为什么这么多内存被用于迭代。

(我在类似但不同的情况下使用ConcurrentBag,我知道在这种情况下使用它是没有意义的)

1 个答案:

答案 0 :(得分:10)

来自ConcurrentBag.GetEnumerator docs(强调我的):

  

枚举表示行李内容的时刻快照。在调用GetEnumerator后,它不会反映对集合的任何更新。该枚举器可以安全地与读取和写入包同时使用。

查看source,您可以看到它创建了一个包的副本:

public IEnumerator<T> GetEnumerator()
{
    // Short path if the bag is empty
    if (m_headList == null)
        return new List<T>().GetEnumerator(); // empty list

    bool lockTaken = false;
    try
    {
        FreezeBag(ref lockTaken);
        return ToList().GetEnumerator();
    }
    finally
    {
        UnfreezeBag(lockTaken);
    }
}

如其名称所示,ToList()会返回List<T>(它不是扩展方法,它是私有成员函数)。

作为旁注,return new List<T>().GetEnumerator();行并不漂亮......可能会改写return Enumerable.Empty<T>().GetEnumerator();