Ed Ball over at Logos Bible Software has written an interested series of articles on events and threads. In part 3, Ed discusses locking around adding and removing delegates and firing the event. There is somewhat of a parallel in the Java world.
Java doesn't have delegates but by convention uses event listeners and the observer pattern. The observable keeps a list of listeners and iterates through them when firing an event. The same issue of locking the list comes up, but if consequences of not locking can be even worse. Most list implementations in Java do not handle the case where a list is changed while it is being iterated over. The iterator is invalidated and will throw a ConcurrentModificationException when hasNext() or next() is called.
One solution is to lock the list while adding and removing listeners and firing the event. This has the same problems that Ed points out in C# that it's scary from a deadlock perspective calling arbitrary code while holding a lock. Another solution in Java is to use the CopyOnWriteArrayList from the java.util.concurrent package or Doug Lea's util.concurrent package. It does exactly what it sounds like it does. This works perfectly for the observer pattern because its iterator is guaranteed to not throw a ConcurrentModificationException. It's expensive to add or remove listeners, but that typically isn't done very often. Before the concurrency tools were available, I would sometimes lock the list, copy it, unlock the list and iterate over the copy to fire the event. This is less performant in most situations because you'll probably be firing events much more often than adding or removing listeners.
Thanks again Ed for the interesting series.