While answering a question about the lock keyword and collections on the microsoft.public.dotnet.languages.csharp newsgroup I realised that I didn't want to have to type all this in again. Therefore, I'm creating a blog entry about this so I can refer people to it in the future.
I have to say that I think lock to be maybe the worst named keyword in the language. It gives the impression that it imposes some kind of invisible force field around the object in question that cannot be pierced by other code until the code block executes. This unfortunately is rubbish.
lock(obj)
{
// some thread sensitive code here
}
translates to
Monitor.Enter(obj);
try
{
// some thread sensitive code here
}
finally
{
Monitor.Exit(obj);
}
All Monitor.Enter (simplistically) does is manipulate the SyncBlock in the objects header. Conceptually It stamps its thread id against the SyncBlock. If another thread id is already stamped there it blocks until the SyncBlock becomes "un-owned" then proceeds to stamp its thread id against the SyncBlock. Monitor.Exit clears the threads id from the syncblock.
So simply stating lock() on anything at all will achieve nothing unless all code that is accessing the rescource also calls lock on the same thing. In other words
lock(obj)
{
// access some shared. thread sensitive resource
}
only guarantees thread synchronization if all code calls lock(obj), obj being the same physical object as above, before accessing the shared resource.
One final thing about lock. lock is a block with no timeout. If you get a thread deadlock your application is hosed. The Monitor class also has a TryEnter method which you can pass a timeout to. Ian Griffiths has written a nice wrapper for this which allows you to use stack based lock acquisition and release, with a timeout, using the using keyword.
Now turning to collections. The collection classes have a SyncRoot member which appears to offer a shortcut to providing thread synchronization. However, consider the following code:
class App
{
static ArrayList arr = new ArrayList();
static void Main(string[] args)
{
arr.Add(new object());
arr.Add(new object());
arr.Add(new object());
arr.Add(new object());
arr.Add(new object());
arr.Add(new object());
Thread t = new Thread(new ThreadStart(Ouch));
t.Start();
lock(arr.SyncRoot)
{
foreach(object o in arr)
{
Thread.Sleep(1000);
o.ToString();
}
}
}
static void Ouch()
{
Thread.Sleep(1000);
arr.RemoveAt(0);
}
}
This throws an InvalidOperationException because the collection was modified during iteration by the Ouch method running on the other thread. In other words, the SyncRoot is useless on its own in the default case. However, add the following line of code just before the creation of the Thread object (or at least before the thread is started).
arr = ArrayList.Synchronized(arr);
This creates a thread safe wrapper around the arraylist with operations locking the SyncRoot internally. Now the code will perform correctly as the foreach is synchronized with the modifications.