2015-07-27 5 views
4

Это проще, если я начну размещая код:C# ConcurrentBag потребление памяти при переборе

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

Я заселен список с 20000000 элементами. В диспетчере задач я вижу, что этот процесс использует ~ 300 МБ. Если я итерацию по списку с помощью цикла Еогеасп:

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

использование памяти не увеличивается (я поставил точку останова на Console.WriteLine и, как я уже сказал, я ее измерения с помощью диспетчера задач) , Теперь, если я заменю список с ConcurrentBag:

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

использование памяти 450 ~ 500МБ до Еогеасп петли. Возникает вопрос: почему, если внутри использования foreach-loop используется переход до ~ 900 МБ?

Я ожидаю, что ConcurrentBag будет потреблять больше памяти по сравнению со списком, но я не понимаю, почему так много памяти используется для итерации.

(я использую ConcurrentBag в аналогичной, но другой сценарий, я знаю, что в данном случае это не имело бы смысл использовать его)

+2

ConcurrentBag необходимо будет сделать какой-то снимок при создании итераторов - поэтому использование более высокой памяти кажется разумным. Проверьте исходный код ссылки, чтобы узнать подробности ... –

+1

Короче говоря: * никогда * повторяйте потокобезопасную коллекцию. Он поглощает память * и * значения, которые вы получаете, часто устаревают. Если вам нужны предсказуемые результаты, то ключевое слово * lock * не будет заменено. –

ответ

9

Из 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();.

+0

Благодарим за быстрый ответ! :) – gcoll

Смежные вопросы