C# lock statement, what object to lock on?

user1885498 picture user1885498 · Dec 7, 2012 · Viewed 66.8k times · Source

I have 3 questions that I need help with.

  1. What are the correct objects/references to be passed as lock statement parameter? I've seen a lot of sample codes and I noticed that the objects/references passed in could possibly be non related to the current class or any other class in the program as long as the access modifier static is non public? E.g.:

    private Object anyObj = new Object();
    lock(anyObj){.....}
    
    private static readonly object Locker = new object();
    lock(Locker){.....}
    

    It just doesn't make sense to me.

  2. I found a sample code in MSDN about multi threading that uses lock statements too. In the sample there are two try/catch blocks with the Monitor.Wait() within it. If I understand the logic correctly, the readerFlag will forbid the program to ever enter the try/catch block at all.
    The code is example 2 from here:
    http://msdn.microsoft.com/en-us/library/aa645740(v=vs.71).aspx

  3. How do I run a thread that runs in the background as long as the Windows Form is active?

Answer

plinth picture plinth · Dec 7, 2012

How and what you lock on depends upon what you're doing.

Let's say that you're working with a device of some kind - say a coffee maker. You might have a class that looks like this:

public CoffeeMaker {
    private IntPtr _coffeeHandle;
    private Object _lock = new Object();
}

In this case, you are protecting access to the _coffeeHandle - a pointer/handle to a real physical device, so this is pretty easy:

public int AvailableCups {
    get {
        lock (_lock) {
            return GetAvailableCups(_coffeeHandle); // P/Invoked
        }
    }
}

public void Dispense(int nCups)
{
    lock (_lock) {
        int nAvail = GetAvailableCups(_coffeeHandle);
        if (nAvail < nCups) throw new CoffeeException("not enough coffee.");
        Dispense(_coffeeHandle, nCups); // P/Invoked
    }
 }

So if I'm running a multithreaded app, I (probably) don't want to read the number of cups that are available while I'm dispensing (maybe it's a hardware error). By protecting accesses to the handle, I can ensure that. Also, I can't be asked to dispense while I'm already dispensing - that would be bad, so that's protected too. Finally, I don't dispense unless I have enough coffee available and you notice that I don't use my public property to check that - this way the action of ensuring there's enough coffee and dispensing are tied together. The magic word is atomic - they can't be cut apart without creating issues.

You use a static object as a lock if you have one and only one instance of a resource that needs protecting. Think, "do I have a singleton?" and that will be a guideline for when you might need a static lock. For example, let's say that CoffeeMaker has a private constructor. Instead, you have a factory method that constructs coffee machines:

static Object _factLock = new Object();

private CoffeeMaker(IntPtr handle) { _coffeeHandle = handle; }

public static CoffeeMaker GetCoffeeMaker()
{
    lock (_factLock) {
        IntPtr _handle = GetCoffeeMakerHandle(); // P/Invoked
        if (_handle == IntPtr.Zero) return null;
        return new CoffeeMaker(_handle);
    }
 }

Now in this case, it feels like CoffeeMaker should implement IDisposable so that handle gets taken care of, because if you don't release it then somebody might not be getting their coffee.

There are a few problems though - maybe if there's not enough coffee, we should make more - and that takes a long time. Heck - dispensing coffee takes a long time, which is why we're careful to protect our resources. Now you're thinking that really all this coffee maker stuff should be in a thread of its own and that there should be an event that gets fired when the coffee is done, and then it starts to get complicated and you understand the importance of knowing what you're locking on and when so that you don't block making coffee because you asked how many cups are there.

And if the words "deadlock", "atomic", "monitor", "wait", and "pulse" all sound foreign to you, you should consider reading up on multiprocessing/multithreading in general and see if you can solve the fair barbershop problem or the dining philosophers problem, both quintessential examples of resource contention.