Events, Delegate and Multithreading

A reader recently asked me about a this common patter, where handler is event

if (Handler != null)  {
   Handler(args);

}

He is right, that this is not threadsafe… to see why I clipped a recent update from the Design Guidelines document…

Thoughts, comments always welcome.

      Do use a protected virtual method to raise each event. This is only applicable to unsealed classes, not to structs or sealed classes.

For each event, include a corresponding protected virtual method that raises the event. The purpose of the method is to provide a way for a derived class to handle the event using an override. Overriding is a more flexible, faster, and a more natural way to handle base class events in derived classes. The name of the method is should start with ‘On’ and be followed with the name of the event.

public class AlarmClock {

   

    public event EventHandler<AlarmRaisedEventArgs> AlarmRaised;

 

    protected virtual void OnAlarmRaised(AlarmRaisedEventArgs e){

        EventHandler<AlarmRaisedEventArgs> handler = AlarmRaised;

        if (handler != null) {

            handler(this, e);

        }

    }

}

The derived class can choose not to call the base implementation of the method in its override. Be prepared for this by not including any processing in the method that is required for the base class to work correctly.

Annotation (Eric Gunnerson):

If you have an event in a class, you need to add a null test before you call the delegate. Typically, you would write:

 

     if (Click != null) Click(this, arg);

 

There is actually a possible race condition here - the event can be cleared between the first and second line. You would actually want to write:

    

     ClickHandler handler = Click;

     if (handler != null) handler(this, arg);

 

You might want to do some other sort of synchronization in other scenarios.

 

So back to the main question. Why is the null test required? We can't change the existing behavior of calling through a delegate as some apps may depend on it, so it would have to be an addition to the language. We have talked about adding an Invoke() keyword to the language to make this easier, but after a fair bit of discussion, we decided that we couldn't do the right thing all the time, so we elected not to do anything.