Debugging eventable code, one stupid mistake and how to fix it.

Okay, so I was debugging a slideshow viewer that I had written because for some reason it was skipping pictures.  I really couldn't figure out why, so I just assumed the timer was somehow being called twice.  Now this took me just a couple of seconds to identify once I had found out the slideshow viewer was performing a skip.

So when did it happen?  Well, I was starting slideshows on a form that I was re-using.  I just reattached a new set of files and told it to go again.  Each time I did this it was re-attaching the event handler.  If you've worked with windows forms before, you know there isn't any way to discover if you've already hooked an event already.  There also isn't any way to remove the event without save the event handler definition somewhere.  So what was the easy fix?  Well, it was to detach the event so I wrote the following code:

// void StartSlideshow()
this.slideshowTimer.Tick += new EventHandler(this.Form_SlideShowAdvance);

// void Form_VisibleChanged()
this.slideshowTimer.Tick -= new EventHandler(this.Form_SlideShowAdvance);

That turns out to be the easiest way.  Is there a better way?  Well, probably.  So let's save the handler definition in a local so we can investigate if it has been hooked up.

// MyClass : Form {
private EventHandler tickEvent = null;

// void StartSlideshow()
if ( tickEvent == null ) {
    tickEvent = new EventHandler(this.Form_SlideShowAdvance);
    this.slideshowTimer.Tick += tickEvent;
} // else it is already hooked

// void Form_VisibleChanged()
if ( tickEvent != null ) {
    this.slideshowTimer.Tick -= tickEvent;
    tickEvent = null;
}

This is actually more powerful I think.  I have a deterministic way to tell if I've hooked an event, and I can unhook it if I want to.  The event takes on a special singleton type quality, because I won't hook the event multiple times and so my event handler won't get called multiple times.  Other code can still hook it with a different handler using the normal syntax, so things behave as normal.  What else can you do with this?  If you wrap the setting of tickEvent into some nice helper functions you can gain the ability to switch your handlers around with minimal code.  I see a lot of discussion online where people want to disable and re-hook event to other handlers and the #1 used syntax is the new EventHandler(*) method which doesn't permit very much control at all.

I would really like a syntax that is a bit more powerful still.  A kind of event manager where all of the events hooked within my type somehow get managed.  The basic scenario I would like automatically solved is:

  1. Set up a tracing handler.
  2. When the user clicks up a menu item the tracing handler's events get hooked by a new form that displays all of the tracing information.
  3. The form gets closed and disposed.
  4. If the trace event gets called again, an exception is thrown because the event handler on the form attempts to access disposed resources.

If the form used some sort of eventing manager when hooking up the events, the programmer doesn't have to explicitly remove those events later.  In highly dynamic eventing scenarios, the code for making sure all of your handlers are detached can get extremely monotonous.  Ideally the event manager might look something like the code shown below.  While I have a small eventing manager programmed up, I'm not happy with it yet.  If I find at some point it is saving me time or provides a syntax others might enjoy I'll try and release it.

// Create a new eventing manager
EventManager em = new EventManager();

// Attach an event to a timer
em.AttachEvent(timer, "Tick", new EventHandler(Timer_Tick), true /* singleton */);

// Try to attach another instance to the singleton.  The attach never occurs
em.AttachEvent(timer, "Tick", new EventHandler(Timer_Tick), true /* singleton */);

// Try to detach tick events
em.DetachEvent(timer, "Tick");

// Detach only a specific handler
em.DetachEvent(timer, "Tick", new EventHandler(Timer_Tick), false /* detach one instance of handler */);
em.DetachEvent(timer, "Tick", new EventHandler(Timer_Tick), true /* detach all instances of handler */);

// Check the state
em.EventAttached(timer, "Tick"); /* return true/false */
em.EventAttached(timer, "Tick", new EventHandler(Timer_Tick)); /* return true/false */

// Dispose()...
em.DetachAllEvents();

Published Wednesday, February 18, 2004 9:01 PM by Justin Rogers
Filed under: ,

Comments

Monday, May 24, 2004 5:57 AM by Dave Hallett

# re: Debugging eventable code, one stupid mistake and how to fix it.

This is an ugly hack, but IIRC, you can just call

this.slideshowTimer.Tick -= new EventHandler(this.Form_SlideShowAdvance

before you call

this.slideshowTimer.Tick += new EventHandler(this.Form_SlideShowAdvance

and if the event is not hooked up, nothing will happen. Not very high-performing code, I'm sure :o) But I think it works.
Monday, May 24, 2004 6:05 AM by Justin Rogers

# re: Debugging eventable code, one stupid mistake and how to fix it.

Definitely a hack. I wound up writing my eventing manager and it works quite well. I tend to use it more often than not in any sort of asynchronous programming scenario with eventing, or when hooking up many hundreds of events in notification scenarios.

You see, the delegate managers tend to work extremely well at ensuring events are hooked in a specific way, and I like that. Take a basic RTC scenario, where you have a group of chatters. Someone owns the group or controls it, while others are members. If the group is now an object with a single broadcast event for sending events, then you could hook that event any number of times through varying code-paths (possibly by mistake), with an event manager I can ensure that it only gets hooked once. I can also allow the hooks to be dependent upon moderation by the group owner. There are so many protections added by the EventManager.
Tuesday, May 25, 2004 3:17 AM by Dave Hallett

# re: Debugging eventable code, one stupid mistake and how to fix it.

You make a good point: the hack is indeed no good at all for such situations. Thanks for the response!

Leave a Comment

(required) 
(required) 
(optional)
(required)