Search code examples
c#multithreadingconcurrent-collections

Concurrent collections eating too much cpu without Thread.Sleep


What would be the correct usage of either, BlockingCollection or ConcurrentQueue so you can freely dequeue items without burning out half or more of your CPU using a thread ?

I was running some tests using 2 threads and unless I had a Thread.Sleep of at least 50~100ms it would always hit at least 50% of my CPU.

Here is a fictional example:

private void _DequeueItem()
{
    object o = null;
    while(socket.Connected)
    {
        while (!listOfQueueItems.IsEmpty)
        {
            if (listOfQueueItems.TryDequeue(out o))
            {
                // use the data
            }
        }
    }
}

With the above example I would have to set a thread.sleep so the cpu doesnt blow up.

Note: I have also tried it without the while for IsEmpty check, result was the same.


Solution

  • It is not because of the BlockingCollection or ConcurrentQueue, but the while loop:

    while(socket.Connected)
    {
        while (!listOfQueueItems.IsEmpty)
        { /*code*/ }
    }
    

    Of course it will take the cpu down; because of if the queue is empty, then the while loop is just like:

    while (true) ;
    

    which in turn will eat the cpu resources.

    This is not a good way of using ConcurrentQueue you should use AutoResetEvent with it so whenever item is added you will be notified. Example:

    private ConcurrentQueue<Data> _queue = new ConcurrentQueue<Data>();
    private AutoResetEvent _queueNotifier = new AutoResetEvent(false);
    
    //at the producer:
    _queue.Enqueue(new Data());
    _queueNotifier.Set();
    
    //at the consumer:
    while (true)//or some condition
    {
        _queueNotifier.WaitOne();//here we will block until receive signal notification.
        Data data;
        if (_queue.TryDequeue(out data))
        {
            //handle the data
        }
    }
    

    For a good usage of the BlockingCollection you should use the GetConsumingEnumerable() to wait for the items to be added, Like:

    //declare the buffer
    private BlockingCollection<Data> _buffer = new BlockingCollection<Data>(new ConcurrentQueue<Data>());
    
    //at the producer method:
    _messageBuffer.Add(new Data());
    
    //at the consumer
    foreach (Data data in _buffer.GetConsumingEnumerable())//it will block here automatically waiting from new items to be added and it will not take cpu down 
    {
        //handle the data here.
    }