BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Parallel and Asynchronous
.NET 1.1+

Thread Synchronisation with the Monitor Class

The lock statement provides a simple way to block access to critical sections of code in multi-threaded and parallel software. When more flexibility is required, the members of the Monitor class can be used to achieve similar thread synchronisation.

Detecting Deadlocks with Monitor.TryEnter

One use of TryEnter is to prevent deadlocks from being permanent. With the lock statement or Monitor.Enter, deadlocked threads never terminate. If you use TryEnter, you can add a timeout and cancel the execution of a thread, reversing any operations and recovering gracefully, when a required lock cannot be obtained.

The code below shows a modified example of the deadlocking threads code from the "Locking for Thread Synchronisation" article. The two threads started in the code deadlock. However, as we've included a timeout, they can react to not being able to obtain the second lock, aborting the operation and reporting the problem to the console.

class Program
{
    static object _lock1 = new object();
    static object _lock2 = new object();

    static void Main(string[] args)
    {
        Thread t1 = new Thread(Thread1);
        Thread t2 = new Thread(Thread2);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();
    }

    static void Thread1()
    {
        lock (_lock1)
        {
            Console.WriteLine("Thread1 locked on _lock1");
            Thread.Sleep(2000);
            if (Monitor.TryEnter(_lock2, TimeSpan.FromSeconds(1)))
            {
                try { Console.WriteLine("Thread1 locked on _lock2"); }
                finally { Monitor.Exit(_lock2); }
            }
            else
            {
                Console.WriteLine("Possible deadlock, _lock1 released");
            }
        }
    }

    static void Thread2()
    {
        lock(_lock2)
        {
            Console.WriteLine("Thread2 locked on _lock2");
            Thread.Sleep(2000);
            if (Monitor.TryEnter(_lock1, TimeSpan.FromSeconds(1)))
            {
                try { Console.WriteLine("Thread2 locked on _lock1"); }
                finally { Monitor.Exit(_lock1); }
            }
            else
            {
                Console.WriteLine("Possible deadlock, _lock2 released");
            }
        }
    }
}

The timeout values in the sample mean that the a likely outcome of running the program is that both threads detect a possible deadlock and abort, giving the following output.

Thread1 locked on _lock1
Thread2 locked on _lock2
Possible deadlock, _lock1 released
Possible deadlock, _lock2 released

If we adjust the timeout values, it is possible to see a result where one of the threads times out and the other then obtains the necessary second lock and completes. The output for such a situation would be similar to the following:

Thread1 locked on _lock1
Thread2 locked on _lock2
Possible deadlock, _lock1 released
Thread2 locked on _lock1

NB: You should be careful with the timeout values when using this approach to cancel deadlocked threads. This method works best when the critical sections execute quickly and the timeouts are small. It is possible that using a timeout that is too short will falsely detect a deadlock, when in reality the thread is simply blocked and would obtain the desired lock if allowed to wait for a longer period.

30 September 2012