views:

1759

answers:

4

I have a bunch of DB entities that are loaded into DB objects. The same DB entity may be loaded into more than DB object. Periodically a DB entity will require special processing. This processing must be performed by one thread at a time. Locking is in order here.

EDIT: Important note: the process calls a slow web service. This is what I'm trying to prevent concurrency. I don't see how this can be done safely w/o locks.

So I create an “padlock” object that will be referenced by the DB objects for locking. The padlock object is entity based so that two or more DB objects for the same entity will use the same padlock object. I’m storing these padlocks in a dictionary object using the DB entity’s ID as the key. The padlock object is just a simple string object as well. Is this the right approach? I'm thinking I'm either over engineering or simplifying this. If the approach is correct, how does this code look? It works, but I've yet to test it under load.

Thanks :)

public static func(CustomObject o)
{
    if (ReadyForUpdate(o))
    {
     lock (LookupPadLockByID(object.ID)
     {
      if (ReadyForUpdate(o))
      {
       PerformUpdate(object);
      }
     }
    }
}

private static readonly object padlockLock = new object();
private static readonly Dictionary<int, string> padLocks = new Dictionary<int,string>();
private static object LookupPadLockByID(int uniqueID)
{
    lock (padlockLock)
    {
     if (!padLocks.ContainsKey(uniqueID))
     {
      padLocks.Add(uniqueID, uniqueID + " is locked");
     }
    }

    return padLocks[uniqueID];
}
+1  A: 

Well, you end up locking on a string, which isn't a good idea (although the concatenation means that interning shouldn't be a huge issue). What is a bigger issue is that:

  • you don't seem to remove the locks from padLocks - is that an issue?
  • you access the dictionary outside the padlockLock; the return should be inside the lock

For this second, this would be simpler:

object itemLock;
if (!padLocks.TryGetValue(uniqueID, out itemLock)) {
    itemLock = new object();
    padLocks.Add(uniqueID, itemLock);
}
return itemLock;

If this code is fairly local (i.e. your objects haven't escaped yet), you might simply lock the record itself? A lot simpler...

Marc Gravell
I need to look into this string interning. You're not the only one to point it out.
A: 

I think you are over engineering. If you only need to protect your DB entities (which I assume is represented by "object" in your code, which I will change to "entity"), you can just use it as your lock. Any reference object can be used as a lock:

public static func(CustomObject o)
{
    if (ReadyForUpdate(o))
    {
        lock (entity)
        {
                if (ReadyForUpdate(o))
                {
                        PerformUpdate(entity);
                }
        }
    }
}
This is interesting, but the simple answer is that I do not want to block a CustomObject that is ready for updating if it is not dependent on another CustomObject being updated. I guess what I'm looking for is a lock manager of sorts.
A: 

If you're using a standard Database of any type, I would suggest dumping these client-side locks entirely in favor of transactions and table/row locks.

What the PeformUpdate method does is call a web service to get the data, and then saves it to the database with the web service data. I need to prevent concurrent calls to the web service and updates to the db.
Why are you using a webservice for access to the database when you know you're going to have transactional updates to the data?
Robert C. Barth
The web service call gets the data from a third party. That data is stored at the DB afterward. I don't begin a DB transaction until the data comes back from the service. I'm not sure I follow what you're asking.
A: 

Locking on a string is not a good idea. I suggest two alternatives:

  1. Use a Dictionary<int,object> as padLocks' type, and put a new object(); as the value.
  2. Create a class that simply holds the id; this would be better for readability if you ever want to look at your LockPad class while debugging.

LockPad class:

class LockPad {
    public int Id { get; private set; }

    public LockPad(int id) {
        this.Id = id;
    }

    public override string ToString() {
        return "lock of " + id.ToString();
    }
}

Then, lock on that class.

configurator