tags:

views:

268

answers:

3

I have followed some tutorials on how to create custem event accessors. This is the code I have:

event ControlNameChangeHandler IProcessBlock.OnControlNameChanged
{
    add
    {
        lock (ControlNameChanged)
        {
            ControlNameChanged += value;
        }
    }
    remove
    {
        lock (ControlNameChanged)
        {
            ControlNameChanged -= value;
        }
    }
}

At the moment the code reaches lock(ControlNameChanged) in the add statament, nothing happens. The code doesn't run any further. However my application is still working. It doesn't freeze or something.

What goes wrong?

+12  A: 

Somehow, someone else holds a lock. You shouldn't use multicast delegate instances or events for locking, and you shouldn't use public members either, because you cannot control who is locking and when.

Therefore I'd use a separate locking object like this:

private readonly object controlNameChangedSync = new object();

event ControlNameChangeHandler IProcessBlock.OnControlNameChanged
{
  add
  {
    lock (controlNameChangedSync)
    {
      ControlNameChanged += value;
    }
  }
  remove
  {
    lock (controlNameChangedSync)
    {
      ControlNameChanged -= value;
    }
  }
}

Note: the reference to the event changes when doing a += or -= on the delegate.

Lucero
Indeed (my now deleted answer says the same thing, basically). The current code isn't thread-safe.
Jon Skeet
Isn't their an implied lock if you leave out the add/remove ? If you don't need to do anything on add/remove just let C# do the locking. Your 'lockchoice' article you say you recommend wrting events with add/remove always but you don't say why.
Martijn Laarman
@Lucero: I don't get it. Why should I use lock? If I comment out the lock statement everything is working well. Can you give me some background information about your solution?
Martijn
@Martijn: If you want to make the add/remove operation on the event handler threadsafe, you need to use the lock. You'd do this if you have an object with an event which must be threadsafe for addition/removal of delegates. From looking at your code, this seemed to be what you want to achieve.Now the lock in your sample locks on a variable which changes (`null` before the first `+=` on the event, and it changes with each `+=` or `-=` afterwards), so that the lock will not behave as you'd expect.
Lucero
@Lucero: So the lock is null the first time and therefore it hangs? And about thread safety, I've copied this code and made it fit in my project. I wasn't aware of the thread safety thing :) But I find it interesting.
Martijn
@Martijn, no it does not hang. But `null` is not a valid argument for `Monitor.Enter` (which is emitted by the compiler for the `lock()`), so that it throws an exception. See http://msdn.microsoft.com/en-us/library/de0542zz.aspx
Lucero
Aah I see, thank you!
Martijn
+2  A: 

The += and -= operators change the delegate. So your Add and Remove methods are locking on different objects each time, and you have no synchronization at all.

Now this wouldn't explain blocking but your question is not very clear on what actually happens. I would expect the program to execute 'normally' with the possibility of race-conditions.

Henk Holterman
+6  A: 

Your code is equivalent to

event ControlNameChangeHandler IProcessBlock.OnControlNameChanged {
    add {
        try {
            Monitor.Enter(ControlNameChanged);
            ControlNameChanged = ControlNameChanged + value;
        }
        finally {
            Monitor.Exit(ControlNameChanged);
        }
    } 
    remove { 
        try {
            Monitor.Enter(ControlNameChanged);
            ControlNameChanged = ControlNameChanged - value;
        }
        finally {
            Monitor.Exit(ControlNameChanged);
        }
    } 
}

Note that the object you Exit is different from the one you enter. This means you have one lock taken which is never released, and one lock is released but never taken. You should change your code into this:

private object padlock = new object();
event ControlNameChangeHandler IProcessBlock.OnControlNameChanged {
    add {
        lock (padlock) {
            ControlNameChanged += value;
        }
    } 
    remove { 
        lock (padlock) {
            ControlNameChanged -= value;
        }
    } 
}
erikkallen
Great explanation! Didn't get it fully until reading your post.
leiflundgren