Should GenericQueue.Enqueue work while a different thread has LOCK(GenericQueue) ?
-
I have a Generic Queue<privateType> object, myGenericQueue. My application receives events from another application. In my event handler, I asynchronously (and therefore on a different thread) enqueue data to myGenericQueue, using a delegate (myDelegate.beginInvoke). I use a callback method to determine if the Enqueue operation succeeded. Both the asynch method and the callback produce debug output. In my app's main(), a Form Timer periodically pings, at which time I LOCK myGenericQueue, enumerate and dequeue it. Again, more debug output. To test for synchronization problems, I put a long sleep in my dequeue loop, so that myGenericQueue will be locked when my event handlers try to Enqueue data. Here's what I'm seeing, that I don't understand: while myGenericQueue is theoretically LOCKed, my event handlers are still having no problems Enqueuing data. The callbacks fire right away, suggesting no delay, and all of the debug output matches order/timing expectations. All data is making it to myGenericQueue. Is this expected? Why is there no collision with the lock? The documentation for the Generic Queue object says: A Queue<T> can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. To guarantee thread safety during enumeration, you can lock the collection during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization. Doesn't this suggest I should be seeing collisions? Here's my dequeuing code:
lock (myGenericQueue)
{
while (myGenericQueue.Count > 0)
{
// copy items from queue to List
myList.Add((privateType)myGenericQueue.Dequeue());// REMOVE: DEBUG: HACK: TODO: remove this, testing only! System.Diagnostics.Debugger.Log(0, "", "Starting 5 sec sleep at: " + DateTime.Now.Ticks + Environment.NewLine); System.Threading.Thread.Sleep(5000); System.Diagnostics.Debugger.Log(0, "", "Coming out of 5 sec sleep at: " + DateTime.Now.Ticks + Environment.NewLine); }
}
-
I have a Generic Queue<privateType> object, myGenericQueue. My application receives events from another application. In my event handler, I asynchronously (and therefore on a different thread) enqueue data to myGenericQueue, using a delegate (myDelegate.beginInvoke). I use a callback method to determine if the Enqueue operation succeeded. Both the asynch method and the callback produce debug output. In my app's main(), a Form Timer periodically pings, at which time I LOCK myGenericQueue, enumerate and dequeue it. Again, more debug output. To test for synchronization problems, I put a long sleep in my dequeue loop, so that myGenericQueue will be locked when my event handlers try to Enqueue data. Here's what I'm seeing, that I don't understand: while myGenericQueue is theoretically LOCKed, my event handlers are still having no problems Enqueuing data. The callbacks fire right away, suggesting no delay, and all of the debug output matches order/timing expectations. All data is making it to myGenericQueue. Is this expected? Why is there no collision with the lock? The documentation for the Generic Queue object says: A Queue<T> can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. To guarantee thread safety during enumeration, you can lock the collection during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization. Doesn't this suggest I should be seeing collisions? Here's my dequeuing code:
lock (myGenericQueue)
{
while (myGenericQueue.Count > 0)
{
// copy items from queue to List
myList.Add((privateType)myGenericQueue.Dequeue());// REMOVE: DEBUG: HACK: TODO: remove this, testing only! System.Diagnostics.Debugger.Log(0, "", "Starting 5 sec sleep at: " + DateTime.Now.Ticks + Environment.NewLine); System.Threading.Thread.Sleep(5000); System.Diagnostics.Debugger.Log(0, "", "Coming out of 5 sec sleep at: " + DateTime.Now.Ticks + Environment.NewLine); }
}
You only mention locking in one place. You have to lock it in both places. Basically, the lock is asking "can I aquire a lock?" and if it can it gets one. If it can't it blocks until it can get one.
Recent blog posts: *SQL Server / Visual Studio install order *Installing SQL Server 2005 on Vista *Crazy Extension Methods Redux * Mixins My Blog
-
I have a Generic Queue<privateType> object, myGenericQueue. My application receives events from another application. In my event handler, I asynchronously (and therefore on a different thread) enqueue data to myGenericQueue, using a delegate (myDelegate.beginInvoke). I use a callback method to determine if the Enqueue operation succeeded. Both the asynch method and the callback produce debug output. In my app's main(), a Form Timer periodically pings, at which time I LOCK myGenericQueue, enumerate and dequeue it. Again, more debug output. To test for synchronization problems, I put a long sleep in my dequeue loop, so that myGenericQueue will be locked when my event handlers try to Enqueue data. Here's what I'm seeing, that I don't understand: while myGenericQueue is theoretically LOCKed, my event handlers are still having no problems Enqueuing data. The callbacks fire right away, suggesting no delay, and all of the debug output matches order/timing expectations. All data is making it to myGenericQueue. Is this expected? Why is there no collision with the lock? The documentation for the Generic Queue object says: A Queue<T> can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. To guarantee thread safety during enumeration, you can lock the collection during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization. Doesn't this suggest I should be seeing collisions? Here's my dequeuing code:
lock (myGenericQueue)
{
while (myGenericQueue.Count > 0)
{
// copy items from queue to List
myList.Add((privateType)myGenericQueue.Dequeue());// REMOVE: DEBUG: HACK: TODO: remove this, testing only! System.Diagnostics.Debugger.Log(0, "", "Starting 5 sec sleep at: " + DateTime.Now.Ticks + Environment.NewLine); System.Threading.Thread.Sleep(5000); System.Diagnostics.Debugger.Log(0, "", "Coming out of 5 sec sleep at: " + DateTime.Now.Ticks + Environment.NewLine); }
}
JoeRip wrote:
Is this expected?
Yes.
JoeRip wrote:
Why is there no collision with the lock?
Because you are not using the lock correctly. The lock doesn't protect the object that you are referring to in any way, the object is only used as an identifier. What the lock does is to prevent another code block locking with the same identifier to be entered (or the same code block to be reentered by another thread). So, to protect your queue, you need a lock around every piece of code that uses the queue.
Despite everything, the person most likely to be fooling you next is yourself.
-
You only mention locking in one place. You have to lock it in both places. Basically, the lock is asking "can I aquire a lock?" and if it can it gets one. If it can't it blocks until it can get one.
Recent blog posts: *SQL Server / Visual Studio install order *Installing SQL Server 2005 on Vista *Crazy Extension Methods Redux * Mixins My Blog
How... odd. The documentation of "lock" at http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx[^] makes no mention at all about having to use lock on both sides. In fact, the example quite clearly shows the lock being applied in only one place, and the lock still preventing another thread from accessing that code...
-
How... odd. The documentation of "lock" at http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx[^] makes no mention at all about having to use lock on both sides. In fact, the example quite clearly shows the lock being applied in only one place, and the lock still preventing another thread from accessing that code...
JoeRip wrote:
In fact, the example quite clearly shows the lock being applied in only one place,
That is because the one place is being used by multiple threads. It is being used on ALL (10) sides, in that example.
Recent blog posts: *SQL Server / Visual Studio install order *Installing SQL Server 2005 on Vista *Crazy Extension Methods Redux * Mixins My Blog
-
JoeRip wrote:
In fact, the example quite clearly shows the lock being applied in only one place,
That is because the one place is being used by multiple threads. It is being used on ALL (10) sides, in that example.
Recent blog posts: *SQL Server / Visual Studio install order *Installing SQL Server 2005 on Vista *Crazy Extension Methods Redux * Mixins My Blog
Yeah, I figured out my error in thinking. I was so used to using
lock (myQueue.SyncRoot)
that I had come to think of it as locking the Queue itself, not setting a lock flag on one of the queue's members. Turns out all I needed to do for my Generic Queues - which I thought didn't have a SyncRoot member, since it didn't show up in VS 2008's Intellisense :-), was to uselock (((ICollection)myGenericQueue).SyncRoot)
{
}It was good to be reminded of what the lock statement was actually doing, though. Thanks!
-
Yeah, I figured out my error in thinking. I was so used to using
lock (myQueue.SyncRoot)
that I had come to think of it as locking the Queue itself, not setting a lock flag on one of the queue's members. Turns out all I needed to do for my Generic Queues - which I thought didn't have a SyncRoot member, since it didn't show up in VS 2008's Intellisense :-), was to uselock (((ICollection)myGenericQueue).SyncRoot)
{
}It was good to be reminded of what the lock statement was actually doing, though. Thanks!
-
No, that doesn't protect the queue. You still have to use a lock around every piece of code that uses the queue.
Despite everything, the person most likely to be fooling you next is yourself.
Agreed, I should have made that clear. I was just explaining why I thought the
lock (object)
call was actually protecting the object. And, I was just noting that thelock (((ICollection)myGenericQueue).SyncRoot)
statement gives me a nice shared object to lock that can be used by my library code and its callers. I was happy to find I could still do this with Generic Queues, the way I had with... uh, non-generic queues. Specific queues?