X

Are lock and Monitor the same in C#?

Working on some threading stuff  I stumbled upon some discussions about lock and Monitor. Some say they are different and some say they are the same. I took few minutes of time to make some simple experiments with both of these. This blog post shows what C# compiler does with lock statement. As I had to use also Monitor class I added one example here how to use it instead of lock.

Demo class with lock and Monitor

Let’s start with a simple demo class that has one method for writing console output in lock and the other method that uses Monitor class for same task.

public class LockDemo
{
    private static object _locker = new object();
    private static bool _hasLock = false;

    public void UseLock()
    {
        lock (_locker)
        {
            Console.WriteLine("Lock");
        }
    }

    public void UseMonitor()
    {
        try
        {
            Monitor.Enter(_locker, ref _hasLock);

            Console.WriteLine("Monitor");
        }
        finally
        {
            Monitor.Exit(_locker);
            _hasLock = false;
        }
    }
}

UseLock() seems better because it’s short and clean. UseMonitor() is more chatty and exposes more details that we usually doesn’t care about. But still we have to know this class when working with multi-threaded code. I will come back to Monitor class later in this post.

Lock after compiling

Leaving out UseMonitor() method let’s see how UseLock() method looks after compiling. I expect you know at least basics of .NET Intermediate Language (IL) in this point. Or good old assembly language. This is UseLock() method in IL.

.method public hidebysig instance void  UseLock() cil managed
{
  // Code size       39 (0x27)
  .maxstack  2
  .locals init (object V_0,
           bool V_1)
  IL_0000:  ldsfld     object WebApplication6.LockDemo::_locker
  IL_0005:  stloc.0
  IL_0006:  ldc.i4.0
  IL_0007:  stloc.1
  .try
  {
    IL_0008:  ldloc.0
    IL_0009:  ldloca.s   V_1
    IL_000b:  call       void [System.Threading]System.Threading.Monitor::Enter(object,
                                                                                bool&)
    IL_0010:  ldstr      "Lock"
    IL_0015:  call       void [System.Console]System.Console::WriteLine(string)
    IL_001a:  leave.s    IL_0026
  }  // end .try
  finally
  {
    IL_001c:  ldloc.1
    IL_001d:  brfalse.s  IL_0025
    IL_001f:  ldloc.0
    IL_0020:  call       void [System.Threading]System.Threading.Monitor::Exit(object)
    IL_0025:  endfinally
  }  // end handler
  IL_0026:  ret } // end of method LockDemo::UseLock

This code is equivalent for UseMonitor() method. It uses static _locker object and local boolean variable to call Monitor.Enter() method. If it doesn’t succeed or if lock is acquired but exception is thrown after this then in finally block lock is always released. I think we just solved one hot arguing topic here.

Monitor class

Monitor class has more to offer and this is why it is not internal class used only by .NET Framework classes. If locker object is already locked and some other thread wants to lock it too then it has to wait until lock is released. There are situations when we don’t threads to wait for a lock.

We can use Monitor class to avoid timer threads for waiting for lock. Instead of ending up with long line of timer callbacks waiting for lock, we can check if lock is present and return immediately if acquiring a lock wasn’t possible. For this we can use TryEnter() method that supports also wait timeout.

public class TimerProcess
{
    private static object _locker = new object();
    private static Timer _timer;

    public void Start()
    {
        _timer = new Timer(Callback, null, 3000, 2000);
    }

    public void Stop()
    {
        _timer.Dispose();
    }

    public void Callback(object state)
    {
        var hasLock = false;

        try
        {
            Monitor.TryEnter(_locker, ref hasLock);
            if (!hasLock)
            {
                return;
            }

            Console.WriteLine(DateTime.Now + ": in lock");
            Thread.Sleep(3000);
        }
        finally
        {
            if (hasLock)
            {
                Monitor.Exit(_locker);
            }
            Console.WriteLine(DateTime.Now + ": can't get lock");
        }
    }
}

In timer callback method we wait in thread for three seconds. Timer interval is two seconds. It is therefore guaranteed that some callback calls find the locker object to be locked.

More about overlapping timer calls. To find out more about how to avoid overlapping timer callbacks read my blog post How to avoid overlapping timer calls? It covers more methods with explanations and source code.

Here is the program class that creates new instance of TimerProcess class and runs it.

class Program
{
    static void Main(string[] args)
    {
        var process = new TimerProcess();
        process.Start();

        Console.WriteLine("Press any key to exit ...");
        Console.ReadKey();

        process.Stop();
    }
}

When running the program we get outout similar to one shown on the following screenshot.

Notice the times on screenshot above. All callbacks that cannot get lock write it out to console and return immediately. It is not 100% optimal solution for all cases like this but it is good example how to avoid timer to generate long row of callbacks waiting their turn to lock the locker object.

Wrapping up

Although there opinions about lock and Monitor from wall to wall we were able to show that actually lock is translated to Monitor and try-finally by compiler. Lock is just a syntactic sugar provided by code editor. Monitor class is not visible to us without a reason. It offers some very useful methods we can use in more complex locking scenarios or when we want to avoid overlapping of timer callbacks.

More compiler secrets

Liked this post? Empower your friends by sharing it!
Categories: .NET C#

View Comments (1)

Related Post