C# – Race Condition Allowing Multiple Transactions and Locking on Objects in a Dictionary

cdata structureslocks

We have a MVC Web API that is called to write transactions for a specific amount, where the amount is debited or credited to a user's balance. This records the balance pre-transaction (PreAmount), the amount, and the balance post-transaction (PostAmount). We noticed from a simple query using SQL Server's LAG and LEAD functions that there were instances of transactions being added from the API were being 'entered too fast' (partially due to the lack of resources put towards buying good hardware for the server, and IIS and SQL running on the same box). We have a mechanism to prevent the transaction from being entered if it exists, but it seems that the method call is being called really quickly, which results in the record being added multiple times due to a race condition.

The way I am going to try to fix this is by using a Dictionary<long, object>. The assumption here is that, we want transactions for a customer to be entered sequentially at all times, but I don't want to lock concurrent requests for multiple customers. The dictionary would contain a customer ID # and the value would be an object I can lock on. When a transaction is about to be entered, I will check to see if there is a customer ID # entry, and if not, enter it in there):

private static Dictionary<long, object> customerLocker = new Dictionary<long, object>();
private static object dictLocker = new object();

Then, when a request to check a balance, or to enter a transaction is requested, I would then lock on that object, and perform the work needed:

lock (dictLocker)
{
    if (!customerLocker.ContainsKey(customerID))
        customerLocker.Add(customerID, new object());
}
lock (customerLocker[customerID])
{
    // ... do the work needed for this customer
}

Is this a good solution to the issue I am having? Will locking on elements inside of a dictionary cause issues? I'm going to try it, but while I'm putting this together, I'd like to see input from others on this scenario.

Best Answer

It's not safe for you to be searching through the dictionary outside of a lock, because by doing so another dictionary could be writing to it at the same time, so your read could end up being corrupted. You need to fetch the value for the key inside of the lock.

Alternatively, you could just use a ConcurrentDictionary to store the objects for each customer instead of locking around access to a Dictionary. This would let you write:

private static ConcurrentDictionary<long, object> customerLocker = 
    new ConcurrentDictionary<long, object>();
lock (customerLocker.GetOrAdd(customerID, () => new object()))
{
    // ... do the work needed for this customer
}