1

got a quick question.

Do I have to use a concurrent queue if one thread is enqueuing and other is dequeuing? Is there any race condition/other risk when using regular container in this scenario (1 reader & 1 writer)?

Shrimp
  • 514
  • 5
  • 9
  • 1
    Yes, you have to. That's what concurrent structures are for - to avoid race conditions. – cantSleepNow Apr 08 '20 at 17:27
  • I was asking, cause I know you have to use such structure when there are multiple writers that access the same element in the container, however scenario with 1 reader and 1 writer wasn't that obvious to me (whether there is actual race condition there) – Shrimp Apr 08 '20 at 17:32
  • 1
    Right... I can't think of it from top of my head but you can write small program and try it. I think the exception you'll get (with regular queue) would be something like queue size/lenght inconsistent.... (not by computer now so can't try it :/ ) – cantSleepNow Apr 08 '20 at 17:34
  • 1
    I deed to state that both operations mutates the query. If you want get the element at the top of the queue without mutating it use `Peek` instead `Dequeue` – Eldar Apr 08 '20 at 19:19

1 Answers1

3

With a ConcurrentQueue you can safely call the methods Enqueue and TryDequeue from multiple threads in parallel. There is no race condition here. You could do it all day long 1,000,000 times per second, with no issues (assuming that you won't consume all the available memory at any moment doing that). There could be a race condition though if you would like to wait for an item to become available, if there is none. For example the consumer thread could run in a loop like this:

while (true)
{
    if (!queue.IsEmpty)
    {
        queue.TryDequeue(out var item); // Race condition!
        Process(item);
    }
    else
    {
        Thread.Sleep(50);
    }
}

This code has a race condition between the calls to IsEmpty and TryDequeue. The queue could be emptied by another thread in the meanwhile. This race condition could be eliminated simply by removing the IsEmpty check:

while (true)
{
    if (queue.TryDequeue(out var item)) // Fixed
    {
        Process(item);
    }
    else
    {
        Thread.Sleep(50);
    }
}

This is inefficient though. The thread will be doing unproductive loops, and when an item becomes available it will take it after a delay. Also notice that the queue has no way of notifying the thread that it has been completed, and will never have any more items again. Both of these problems are solved with the specialized BlockingCollection class.

foreach (var item in blockingCollection.GetConsumingEnumerable())
{
    Process(item);
}

The GetConsumingEnumerable method ensures instant notification for either a new item, or for the completion of the collection.

The BlockingCollection class has a drawback though. As its name suggests, it blocks the current thread during the waiting. If this is something that you would like to avoid, you can look here for a quick summary of the asynchronous alternatives.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104