C Sharp

Protecting Code by Using the Monitor Class

The System.Monitor class enables you to serialize the access to blocks of code by means of locks and signals. For example, you have a method that updates a database and that cannot be executed by two or more threads at the same time. If the work being performed by this method is especially time-consuming and you have multiple threads, any of which might call this method, you could have a serious problem on your hands. This is where the Monitor class comes in. Take a look at the following synchronization example. Here we have two threads, both of which will call the Database.SaveData method.

using System;
using System.Threading;
class Database
{
    public void SaveData(string text)
    {
        Console.WriteLine("Database.SaveData - Started");
        Console.WriteLine("Database.SaveData - Working");
        for (int i = 0; i < 100; i++)
        {
            Console.Write(text);
        }
        Console.WriteLine("\nDatabase.SaveData - Ended");
    }
}
class ThreadMonitor1App
{
    public static Database db = new Database();
    public static void WorkerThreadMethod1()
    {
        Console.WriteLine("Worker thread #1 - Started");
        Console.WriteLine
            ("Worker thread #1 -Calling Database.SaveData");
        db.SaveData("x");
        Console.WriteLine("Worker thread #1 - Returned from Output");
    }
    public static void WorkerThreadMethod2()
    {
        Console.WriteLine("Worker thread #2 - Started");
        Console.WriteLine
            ("Worker thread #2 - Calling Database.SaveData");
        db.SaveData("o");
        Console.WriteLine("Worker thread #2 - Returned from Output");
    }
    public static void Main()
    {
        ThreadStart worker1 = new ThreadStart(WorkerThreadMethod1);
        ThreadStart worker2 = new ThreadStart(WorkerThreadMethod2);
        Console.WriteLine("Main - Creating worker threads");
        Thread t1 = new Thread(worker1);
        Thread t2 = new Thread(worker2);
        t1.Start();
        t2.Start();
    }
}

When you compile and execute this application, you'll see that the resulting output will have a mixture of o's and x's, showing that the Database.SaveData method is being run concurrently by both threads. (Note that once again I've abbreviated the output.) -

Main - Creating worker threads
Worker thread #1 - Started
Worker thread #2 - Started
Worker thread #1 - Calling Database.SaveData
Worker thread #2 - Calling Database.SaveData
Database.SaveData - Started
Database.SaveData - Started
Database.SaveData - Working
Database.SaveData - Working
xoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxox
Database.SaveData - Ended
Database.SaveData - Ended
Worker thread #1 - Returned from Output
Worker thread #2 - Returned from Output

Obviously, if the Database.SaveData method needed to finish updating multiple tables before being called by another thread, we'd have a serious problem.

To incorporate the Monitor class in this example, we use two of its static methods. The first method is called Enter. When executed, this method attempts to obtain a monitor lock on the object. If another thread already has the lock, the method will block until that lock has been released. Note that there is no implicit box operation performed here, so you can supply only a reference type to this method. The Monitor.Exit method is then called to release the lock. Here's the example rewritten to force the serialization of access to the Database.SaveData method: -

using System;
using System.Threading;
class Database
{
    public void SaveData(string text)
    {
        Monitor.Enter(this);
        Console.WriteLine("Database.SaveData - Started");
        Console.WriteLine("Database.SaveData - Working");
        for (int i = 0; i < 100; i++)
        {
            Console.Write(text);
        }
        Console.WriteLine("\nDatabase.SaveData - Ended");
        Monitor.Exit(this);
    }
}
class ThreadMonitor2App
{
    public static Database db = new Database();
    public static void WorkerThreadMethod1()
    {
        Console.WriteLine("Worker thread #1 - Started");
        Console.WriteLine
           ("Worker thread #1 - Calling Database.SaveData");
        db.SaveData("x");
        Console.WriteLine("Worker thread #1 - Returned from Output");
    }
    public static void WorkerThreadMethod2()
    {
        Console.WriteLine("Worker thread #2 - Started");
        Console.WriteLine
            ("Worker thread #2 - Calling Database.SaveData");
        db.SaveData("o");
        Console.WriteLine("Worker thread #2 - Returned from Output");
    }
    public static void Main()
    {
        ThreadStart worker1 = new ThreadStart(WorkerThreadMethod1);
        ThreadStart worker2 = new ThreadStart(WorkerThreadMethod2);
        Console.WriteLine("Main - Creating worker threads");
        Thread t1 = new Thread(worker1);
        Thread t2 = new Thread(worker2);
        t1.Start();
        t2.Start();
    }
}

Notice in the following output that even though the second thread called the Database.SaveData method, the Monitor.Enter method caused it to block until the first thread had released its lock: -

Main - Creating worker threads
Worker thread #1 - Started
Worker thread #2 - Started
Worker thread #1 - Calling Database.SaveData
Worker thread #2 - Calling Database.SaveData
Database.SaveData - Started
Database.SaveData - Working
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Database.SaveData - Ended
Database.SaveData - Started
Worker thread #1 - Returned from Output
Database.SaveData - Working
ooooooooooooooooooooooooooooooooooooooo
Database.SaveData - Ended
Worker thread #2 - Returned from Output