Friday, June 09, 2006 #

Events, Multithreading and Deadlocks

I made a post today about this sad story on the new MSDN Wiki...

The problem occures when using events in a multithreaded program.
You application get stuck from time to time and when you break execution with you debugger, you find it stopped on a line like this :
AnObject.AnEvent -= new EventHandler(Event_Hanlder);

Wtf ?! Why is this line blocking ?! This is just a -= operator, it cannot block execution !!

So lets take a look at how events are implemented.
Events manage internaly a list of delegates, and protect this list against concurrent access. Thus, the list is locked in two main cases :

  • When raising an event. The event list is locked until returning from the event handler. This prevent the list from changing while enumerating through the handlers.
  • When subscribing or unsubscribing from an event. The prevents the list to be modified from different places.

It's clearer now why deadlocks can occure.
When unsubscribing from an event EventB in an event handler called through EventA, your thread holds two locks, first one on EventA, then one on EventB.
If another thread does the contrary (unsubscribe from EventA in EventB), then it will first lock EventB, then try to lock EventA.

Obviously if the second thead has already locked EventB before the first thread does, the first thread will block on the unsubscription from EventB until the second thread leaves the event handler. But since the second thread will also block on the unsubscription from EventA until the first thread leaves the event handler, a nasty deadlock occures.
You can refere to the sample code in the post on the MSDN Wiki.

It is not realy kind of event implementers to have hidden locks in the code. There would be a good way to remove one of the two lock reasons : instead of locking the event while calling event handlers, it would be possible to lock the event while making a copy of the handlers, then call the event handlers from the list copy. This way the list cannot be modified during the call, and there is no risk of reentrant calls.
The other solution is to unsubscribe the events out the the event handler thread by launching new threads to perform that task, but it makes the execution far less predictive...

If you have better solution, just tell me !

posted @ 2:05 PM | Feedback (23)