Search code examples
c#queueproducer-consumerautoresetevent

Consumer/Producer with AutoResetEvent


I have the below codes in C# for consumer and producer using AutoResetEvent, but they do not work in case having multiple producer and one consumer. The problem is that the consumer can not consume all the items in the queue. When I debug, I notice the consumer can only remove one item and then it returns false and can not remove anymore. Seems the problem is in AutoResetEvent, but I can not figure out what is wrong.

private AutoResetEvent newItemSignal = new AutoResetEvent(false);
private Queue<Task> iQueue = new Queue<Task>();

public void Enqueue(Task task)
{
    lock (((ICollection)iQueue).SyncRoot)
    {
        iQueue.Enqueue(task);
        newItemSignal.Set();
    }
}



public bool Dequeue(out Task task, int timeout)
{
    if (newItemSignal.WaitOne(timeout, false))
    {
        lock (((ICollection)iQueue).SyncRoot)
        {
            task = iQueue.Dequeue();
        }
        return true;
    }
    task = default(Task);
    return false;
}

Solution

  • The problem with using an AutoResetEvent like this is that you may call Set() twice or more but WaitOne() only once. Calling Set() on an ARE that is already signaled will always fail, the item gets stuck in the queue. A standard threading race bug. Looks like you could fix it by emptying the entire queue in the consumer. Not a real fix, the producer can still race ahead of the consumer, you merely lowered the odds to the once-a-month undebuggable stage.

    ARE cannot do this, it cannot count. Use a Semaphore/Slim instead, it was made to count in a thread-safe way. Or use a ConcurrentQueue, a class added to solve exactly this kind of programming problem.