views:

339

answers:

7

I have a scenario where

  • multiple threads are pushing data on a Queue

  • only ONE thread is processing data using code below

code -

  while ( Continue )
  {
        while ( queue.Count > 0 )
        {
             MyObj o = queue.Dequeue();
             someProcess(o);
        }
        myAutoResetEvent.WaitOne();
  }

But sometimes, queue.Dequeue() returns null in the scenario above What gives ?

+2  A: 

Make sure nothing's pushing null values into the queue. nulls are allowed as queued values. Also, according to this document, only Queue<T>'s static members are thread-safe, so beware of doing reading and writing across threads.

Jacob
You're misunderstanding that document. It's saying that only the static _members_ of `Queue` are guaranteed to be thread-safe. Assigning a Queue to a static variable does not magically make it thread-safe.
SLaks
there are no nulls being pushed in
Kumar
Following up on SLaks' comment: `Queue<T>` does not have any static members. That bit of the documentation is irrelevant boilerplate (for the current version of the class, at least - the versions in the 2.0 and 3.0 frameworks each had one static method, `ReferenceEquals`).
Jeff Sternal
Thanks, @SLaks and @Jeff. That makes a lot more sense.
Jacob
+6  A: 

You need to synchronise the access to the queue. Put lock statements around all code sections that access the queue (both reading and writing). If you access the queue simultaneously from multiple threads the internal structures may be corrupted and just about anything can happen.

Guffa
if "access" means reading, there's only 1 readerrest(2) are writers
Kumar
@Kumar: Having two writers (on separate threads) add to a collection that's not inherently thread-safe is going to cause issues. Guffa's right; you need to synchronize access. See my answer for a sort of casual explanation.
Dan Tao
Hmm... would you be able to use a `java.util.concurrent.SynchronousQueue` instead of lock statements? I haven't dealt with this class yet.
R. Bemrose
@R. Bemrose - not in C#. ;)
Jeff Sternal
If you're using .NET 4, you can avoid locking by using the ConcurrentQueue type: http://www.codethinked.com/post/2010/02/04/NET-40-and-System_Collections_Concurrent_ConcurrentQueue.aspx
Judah Himango
Note that in this scenario an exclusive lock makes sense. If the scenario were many readers, one writer, instead of the opposite, then a ReaderWriterLockSlim would be a better solution.
Eric Lippert
+2  A: 

You need to read this blog post.

Also, here's a very minimal skeleton of a "channel" for communication between threads:

public class Channel<T>
{
    private readonly Queue<T> _queue = new Queue<T>();

    public void Enqueue(T item)
    {
        lock (_queue)
        {
            _queue.Enqueue(item);
            if (_queue.Count == 1)
                Monitor.PulseAll(_queue);
        }
    }

    public T Dequeue()
    {
        lock (_queue)
        {
            while (_queue.Count == 0)
                Monitor.Wait(_queue);

            return _queue.Dequeue();
        }
    }
}
Daniel Earwicker
Looks interesting but i'd prefer to use locking minimally, perhaps time to finally go through the powerthreading library in detail
Kumar
If you want to use a standard container from multiple threads, the above code is minimal! But powerthreading is worth a look anyway.
Daniel Earwicker
A: 

Why don't you solve the problem that way?

while (IsRunning)
{
    do
    {
        MyObj myObj = queue.Dequeue();
        if (myObj != null)
        {
            DoSomethingWith(myObj);
        }
    } while (myObj != null);

    myAutoResetEvent.WaitOne();
}

Update

Ok, after reading Earwickers comment and all the other answers, you are all right and i'm just false. So please don't use the code above in multithreaded contexts .

Oliver
Because the `Dequeue` method of `Queue` isn't thread safe. If a reader is trying to do this at the same time as a writer is trying to insert something, the results will be... unpredictable.
Daniel Earwicker
+6  A: 

You say:

multiple threads are pushing data on a Queue

The Queue<T>.Enqueue method is not thread-safe. This means that work gets done within the Enqueue method that needs to be synchronized if multiple threads are calling it. A simple example would be updating the Count property. It's a safe bet that somewhere in the Enqueue method there's a line that looks something like this:

++count;

But as we all know, this isn't an atomic operation. It's really more like this (in terms of what's actually happening):

int newCount = count + 1;
count = newCount;

So say the count is currently 5, and Thread 1 gets past int newCount = count + 1... then Thread 1 thinks, "OK, the count is now 5, so I'll make it 6." But the very next operation that gets executed is where Thread 2 gets to int newCount = count + 1 and thinks the same thing as Thread 1 ("the count is now 6"). So two items have just been added to the queue, but the count only went from 5 to 6.

This is just a very basic example of how a non thread-safe method like Queue<T>.Enqueue can get messed up when access is not synchronized. It doesn't specifically explain what's happening in your question; my intention is simply to point out that what you're doing is not thread-safe and will cause unexpected behavior.

Dan Tao
+2  A: 

Guffa is correct, having multiple threads reading and writing to the queue will cause problems, because Queue<T> is not thread safe.

If you're on .NET 4, use the ConcurrentQueue<T> class, which is thread safe. If you're not on .NET 3 or earlier, you can either do your own locking, as Guffa pointed out, or use a 3rd party library.

Judah Himango
ConcurrentQueue<T> looks very good, any port of that class in .net 3.5 world ?
Kumar
Yes, there is a port from Microsoft. It's inside the RX framework for .NET 3.5: http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx
Judah Himango
A: 

If you happen to be using non-generic Queue (not that I advise to use it), you can use Queue.Synchronized method to get a thread-safe wrapper:

Queue queue = Queue.Synchronized(new Queue());

Otherwise you should take care of locking yourself, as others suggest.

VladV