What is the lock statement (monitor) in C#? and what you should look out for

In this post I will show how the lock statement in C# works and what you should look out for when using it. It is well described on MSDN as a statement that "acquires the mutual-exclusion lock for a given object, executes a statement block, and then releases the lock". So in a multi-threaded environment you can control that two threads do not access the same object at the same time, which could result in concurrency issues. This is done by blocking a thread from acquiring the lock if another thread has it.

The lock statement and how it is related to monitor

There is a strong correlation between the Monitor class and lock. The lock statement is basically sugar syntax for a monitor wrapped in a Try/Finally clause:

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    // body
}
finally
{
    if (lockWasTaken)
    {
        Monitor.Exit(temp); 
    }
}

In the above you can see that it enters and exits the monitor, which is the critical section. I would strongly advise against using the monitor class on your own, you should rather use the lock statement unless you know exactly what you are doing. My reason is that you can easily forget calling Exit() on the monitor, the lock statement takes care of this for you - it is also a lot easier to read.

An example of locking

I have created a small example below with two threads that uses the same string variable. Both overwrites the value, but the first thread sleeps for a second. None of the threads lock the string object and since the first one sleeps, the second one writes its value first and the first thread writes it secondarily. Just as you would expect:

string s = "SomeValue";

new Thread(() => {
    Thread.Sleep(1000);
    s = "Changed value in first thread";
    Console.WriteLine(s);
}).Start();

new Thread(() => {
    s = "Changed value in second thread";
    Console.WriteLine(s);
}).Start();

/*
Result:
Changed value in second thread
Changed value in first thread
*/

In the next example only the first thread locks the string object. I have seen some code implementations where it was thought that this was enough, however it is not. The second thread still writes its value first. This is due to the second thread not having a lock, so there is no critical section for this thread - it never tries to acquire a lock:

string s = "SomeValue";

new Thread(() => {
    lock (s) { //new lock added
        Thread.Sleep(1000);
        s = "Changed value in first thread";
        Console.WriteLine(s);
    }
}).Start();

new Thread(() => {
    s = "Changed value in second thread";
    Console.WriteLine(s);
}).Start();
/*
Result:
Changed value in second thread
Changed value in first thread
*/

In my last example both threads have a lock clause, in this scenario the first thread writes its value first. The second thread has to wait a second until the first thread is done, then it gets to write its value as well:

string s = "SomeValue";

new Thread(() => {
    lock (s) { 
        Thread.Sleep(1000);
        s = "Changed value in first thread";
        Console.WriteLine(s);
    }
}).Start();

new Thread(() => {
    lock (s) //new lock added
    {
        s = "Changed value in second thread";
        Console.WriteLine(s);
    }
}).Start();

/*
Result:
Changed value in first thread
Changed value in second thread
*/

There is a small catch here, the second thread may lock first, as they are started right after one another. When I ran it, it happened rarely but it may vary depending on the hardware it is run on. Nonetheless they mutually exclude one another from their critical sections.

I hope these examples helped you understand the lock statement better.

I used threads in my examples above, it may have been simpler to use Tasks, but since this post is on threads I went with that.

Some things to look out for

When working with locks there are some rules of thumb when it comes to the object being locked. First of all it should not be a public (returned) object as this may be used elsewhere and locked there as well, this can cause deadlocks.

Second it has to be a value type. The lock statement takes and object, if you pass a value type it will be boxed as an object (reference type). This means every time lock is called it would lock on a different object, not locking anything at all. However you will not be able to do this as you will encounter an error like "xyz is not a reference type as required by the lock statement".

That is it

So in summary, be careful not to:

  • use the monitor class directly, use the lock statement instead
  • use a lock on a public or returned object
  • use a lock on a value type

I hope this post on the lock statement can help you avoid some locking pitfalls and make you understand the lock keyword better, let me know in the comments if it did!