3

I have a situation, where sometimes sleeping threads are not woken on a Monitor.PulseAll(object lock) command. The phenomenon is not deterministic. In general it works, but sometimes during debbuging, the sleeping threads fail to wake and my queue keeps filling up.

The working thread is put to sleep, if the queue is empty:

private void Dequeue()
{
    try
    {
        while (true)
        {
            T item;

            lock (_lock)
            {
                if (_queue.Count == 0)
                {
                    Monitor.Wait(_lock);  //thread stays asleep here. why?
                }
                item = _queue.Dequeue(); //break point [1]
            }
        }
    }
    catch (ThreadAbortException ex)
    {
        _logger.Error(ex)
    }
}

Upon Add the queue has at least one item and PulseAll on the lock-object should wake the threads.

public void Add(T item)
{
    Validate.NotNull(item, "item must not be null");

    lock (_lock)
    {
        _queue.Enqueue(item);
        _queueInfoAdministrator.IncrementCount();
        Monitor.PulseAll(_lock);
    }
}

Does anyone else have similar experiences or could point me in the right direction, as to why this happens (sometimes)?

EDIT 2011.04.08: Further information - there is only one consumer thread. So theoretically a Pulse would suffice. Once the state has been reached, where the consumer thread stays sleeping, I can continue to enqueue items and subsequently call PulseAll without being able to wake the sleeping thread. I placed a break point [1], which never gets hit in the described situation. Therefore I believe it is not a deadlock problem as described in the MSDN pages to Pulse/Monitor.

froeschli
  • 2,692
  • 2
  • 28
  • 55

3 Answers3

1

Could it be you are pulsing when the Dequeue function is not waiting? If so the wait will not be triggered until the next pulse.

See the important note on Pulse

Russell Troywest
  • 8,635
  • 3
  • 35
  • 40
  • As I mentioned above, the queue fills up. I can pulse all I want, the thread remains at the position marked with a comment. I can see that in Visual Studio 2008. – froeschli Apr 04 '11 at 15:11
1

Are you sure that the threads are asleep? Your code has a call to _queue.Dequeue, which is going to throw an exception if there are no items in the queue. So if two threads are waiting on the lock, when the Add method enqueues an item and calls PulseAll, both threads will be released. The first thread will call Dequeue and get the only item in the queue. The next thread will call Dequeue and throw an exception. If you're catching and swallowing that exception, you'll never see it. And since the exception has escaped the while loop, the thread terminates and the queue will begin to fill.

Also, although I doubt this is an issue in your case, see http://msdn.microsoft.com/en-us/library/system.threading.monitor.pulse.aspx, where it talks about potential deadlock using Pulse and PulseAll.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • There is only one consumer to the queue, so I doubt that any Exception gets thrown. All the while, I would hit a breakpoint when a thread tries to dequeue an item. – froeschli Apr 04 '11 at 15:13
  • The issue has been resolved through a change in architecture. I have accepted this answer, because of the information provided. – froeschli Feb 28 '12 at 13:53
0

the Wait call should always be used in a While loop that waits for the condition. The original code does not use a While loop, but an If statement.

lock (_lock) { // wrong: if (_queue.Count == 0) while (_queue.Count == 0) { Monitor.Wait(_lock); //thread stays asleep here. why? }

for a detailed explanation see the section "Signaling with Wait and Pulse" of part 4 in this article: Threading in C#

TmTron
  • 17,012
  • 10
  • 94
  • 142