AppBeat Blog

Perfect Monitoring for Your Cloud

C# - how to lock without deadlocks

In C# there is built-in keyword lock which enables you to create thread-safe methods. However, you must be aware that if you are not careful, you can create deadlock conditions where your method waits for lock indefinitely.

How lock keyword works? As described in "Locks and exceptions do not mix" blog article, in C# 4 compiler translates lock statement into this:

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

We can use this to create utility method which works similarly to original lock, but waits only specified amount of time and prevents deadlocks:

using System;
using System.Threading;

namespace AppBeat.Utility
{
    public static class Concurrency
    {
        /// <summary>
        /// Method tries to run <seealso cref="doAction"/> in thread-safe manner but waits max <seealso cref="millisecondsTimeout"/> milliseconds for lock. If it can not acquire lock in specified time, exception is thrown.
        /// This method can be used to prevents deadlock scenarios.
        /// </summary>
        public static void LockWithTimeout(object lockObj, Action doAction, int millisecondsTimeout = 15000)
        {
            bool lockWasTaken = false;
            var temp = lockObj;
            try
            {
                Monitor.TryEnter(temp, millisecondsTimeout, ref lockWasTaken);
                if (lockWasTaken)
                {
                    doAction();
                }
                else
                {
                    throw new Exception("Could not get lock");
                }
            }
            finally
            {
                if (lockWasTaken)
                {
                    Monitor.Exit(temp);
                }
            }
        }
    }
}

Instead of this code:

lock (this)
{
    //do something
}

You could then have something like this:

AppBeat.Utility.Concurrency.LockWithTimeout(this, delegate () {
    //do something
}, 15000); //do not wait more than 15 seconds for lock

As you can see, usage is very similar but in second case we should never get deadlock, because code will simply throw exception if it can not get lock.

This code could be used in debug version when you stress test your multithreaded code, so you can see if you get any timeout exceptions (deadlocks occur). From stack trace you should be able to see which part of your code is problematic.

In production you can leave this or replace it back with standard lock statement.

Comments are closed