View Single Post
 
Old 09-08-2007, 08:25 PM
AtmaWeapon's Avatar
AtmaWeapon AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Example 3 - Thread Safety

Example 3 is similar to Example 2, but it has been modified to be more thread safe. We lock on the events collection when we want to work with it. This does mean that we could block if many classes are trying to subscribe at once, but since we are locking on an instance object at least we aren't blocking other, unrelated instances.

It may seem like an alternative would be to lock on the event keys, but there are two problems with this. First, EventHandlerList is not a thread-safe class, so if we don't lock on it the results could get unpredictable in a hurry. Second, since the object keys are static, when we lock on them we inadvertently cause the lock on all instances of the class.

Another subtlety here involves how RaiseEvent is implemented. In the default event implementation it's customary to see something like this:
Code:
Protected Sub OnSomethingHappened(ByVal e as EventArgs)
    If Not SomethingHappened Is Nothing Then
        RaiseEvent SomethingHappened(Me, e)
    End If
The problem here is subtle and rare, but must be addressed. Remember, in a preemptive multitasking environment like Windows, any process or thread can be interrupted at any point. If our thread is interrupted after the If statement, and before this thread returns to execution something removes all of the event handlers from SomethingHappened, then a NullReferenceException will be thrown! This can be handled via a simple change:
Code:
Dim handler as EventHandler = SomethingHappened
If Not handler is Nothing
    RaiseEvent(SomethingHappened(Me, e))
End If
By keeping a private reference to the event delegate, we insure that even if someone manages to alter the delegate in the EventHandlerList then we will still have the old object.

The RaiseEvent sections of the custom events have been modified to reflect this.

Example 4 - Handling exceptions appropriately

This is kind of nasty and I'm not sure I'd suggest using it in all cases, but since I'm covering everything I can think of it's worth mentioning.

What happens if one of your event handlers throws an exception? In the default implementation, the underlying event delegate is a multicast delegate, and when its Invoke method is called each event subscriber is called one by one. If one of these handlers throws an exception, then none of the subscribers that come after that one will execute. This is pretty bad, and there is a solution but I'm not too pleased with it.

Take a look at Example 4, in particular the large comment I left in ExceptionEventClass. If we get the invocation list of the delegate, then we can execute each subscriber method one by one. The only problem with the example implementation is that we eat the exception; if we rethrow it then execution would halt and we'd cause the problem we're trying to solve. I've tried to find a good solution to this problem and it appears a good solution doesn't really exist. One could definitely create a structure with a delegate member and an exception member, then create one of these structures for each exception that fires, and at the end of invocation do something with this list to indicate to the caller that exceptions happened, but there's no clear way to bubble the exception back to the subscriber itself.

I believe the lesson we take away from this is that if your class is going to subscribe to events, it must take care to handle all exceptions that could be thrown itself. Suppose you were subscribing to events from a singleton class that gives information about a database, and several other classes subscribe to this event; if your class doesn't handle its exceptions, you could cause all of the other subscribers to never see the event! I think it should probably be a good rule of thumb to never throw exceptions from an event handler.

Appendix A - An Event Dictionary Class

The appendix project demonstrates a class that uses an internal instance of Dictionary to store and raise events. This will be more efficient than using EventHandlerList because of the faster lookup. There's not much to say about it because it is not a spectacularly difficult thing to implement.

Appendix B - Running the Examples

Since the forum places some pretty strict guidelines on what files can and cannot be posted, I was forced to separate the examples into several zip files. Some of the examples used a couple of common classes, and I've included them with each example zip file. You should be able to run the example by creating a new Windows Forms project, deleting the auto-generated Form1.vb, adding all of the files from the zip file (both folders), then setting your project to use the right startup object. If you have any problems with this or there are missing files, let me know immediately.
Attached Files
File Type: zip Example 1.zip (2.2 KB, 56 views)
File Type: zip Example 2.zip (3.1 KB, 47 views)
File Type: zip Example 3.zip (2.5 KB, 45 views)
File Type: zip Example 4.zip (3.3 KB, 43 views)
File Type: zip Appendix.zip (3.9 KB, 50 views)
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote