Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Are Lock and Monitor the Same in C#?

DZone's Guide to

Are Lock and Monitor the Same in C#?

In the world of concurrency, locks and monitors may seem the same to some, and different to others. We take a look at how they compare in C#.

· Performance Zone ·
Free Resource

Container Monitoring and Management eBook: Read about the new realities of containerization.

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 the Monitor class for the 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 don’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 the UseMonitor() method, let’s see how the UseLock() method looks after compiling. I expect you know at least the basics of .NET Intermediate Language (IL) at this point. Or a good old assembly language. This is the 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 the UseMonitor() method. It uses the static _locker object and a local boolean variable to call the Monitor.Enter() method. If it doesn’t succeed, or if lock is acquired but an exception is thrown after this, then block lock is always released. I think we just solved one hot topic here.

Monitor Class

The Monitor class has more to offer and this is why it is not an internal class used only by .NET Framework classes. If the 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 want threads to wait for a lock.

We can use the Monitor class to avoid timer threads waiting for lock. Instead of ending up with a 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 the TryEnter() method that also supports the 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 the timer callback method, we wait in the thread for three seconds. The timer interval is two seconds. It is, therefore, guaranteed that some callback calls find the locker object to be locked.

Here is the program class that creates a new instance of the 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 an output similar to the one shown in the following screenshot.

Overlapping timer callbacks

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

Wrapping Up

Although there're 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 the compiler. Lock is just a syntactic sugar provided by the code editor. The 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 timer callbacks.

More Compiler Secrets

Take the Chaos Out of Container Monitoring. View the webcast on-demand!

Topics:
monitor ,lock ,callbacks ,concurrency ,performance

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}