简体   繁体   English

迭代时C#ConcurrentBag内存消耗

[英]C# ConcurrentBag memory consumption when iterating

It's easier if I start by posting the code: 如果我从发布代码开始,这会更容易:

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

I've populated a List with 20,000,000 elements. 我已经填充了一个包含20,000,000个元素的List。 I see in the Task Manager that the process is using ~300MB. 我在任务管理器中看到该进程正在使用~300MB。 If I iterate through the list using a foreach loop: 如果我使用foreach循环遍历列表:

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

memory usage does not increase (I've put a breakpoint on Console.WriteLine and, as I said, I'm measuring it using the Task Manager). 内存使用率没有增加(我在Console.WriteLine上设置了一个断点,正如我所说,我正在使用任务管理器测量它)。 Now, if I replace the List with a ConcurrentBag: 现在,如果我用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);
    }
}

memory usage is 450~500MB before the foreach-loop. 在foreach循环之前,内存使用量为450~500MB。 The question is: why if inside the foreach-loop usage jumps to ~900MB? 问题是:为什么如果在foreach循环使用内部跳到~900MB?

I expect the ConcurrentBag to consume more memory compared to the List, but I don't understand why so much memory is being used for the iteration. 我希望ConcurrentBag与List相比消耗更多内存,但我不明白为什么这么多内存被用于迭代。

(I'm using the ConcurrentBag in a similar but different scenario, I know that in this case it would not make sense to use it) (我在类似但不同的情况下使用ConcurrentBag,我知道在这种情况下使用它是没有意义的)

From the ConcurrentBag.GetEnumerator docs (emphasis mine): 来自ConcurrentBag.GetEnumerator文档 (强调我的):

The enumeration represents a moment-in-time snapshot of the contents of the bag. 枚举表示行李内容的时刻快照 It does not reflect any updates to the collection after GetEnumerator was called. 调用GetEnumerator后,它不会反映对集合的任何更新。 The enumerator is safe to use concurrently with reads from and writes to the bag. 枚举器可以安全地与读取和写入包同时使用。

Looking at the source , you can see it creates a copy of the bag: 查看源代码 ,您可以看到它创建了一个包的副本:

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);
    }
}

Like its name implies, ToList() returns a List<T> (it's not the extension method, it's a private member function). 就像它的名字所暗示的那样, ToList()返回一个List<T> (它不是扩展方法,它是一个私有成员函数)。

As a side note, that return new List<T>().GetEnumerator(); 作为旁注, return new List<T>().GetEnumerator(); line isn't pretty... that could have written return Enumerable.Empty<T>().GetEnumerator(); line不漂亮...可以写入return Enumerable.Empty<T>().GetEnumerator(); instead. 代替。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM