As we get closer to shipping WPF, I get more and more wary of what I call heavy-handed fixes. Places where in order to fix a corner case, we disallow another case that works perfectly well. One example is a bug we found recently, where if you do a nested message loop in the middle of processing the IsMouseOver property change, you potentially receive the MouseLeave before the MouseEnter. Because both MouseEnter and the IsMouseOver property are tied to one window message, but because of that nested message loop, we don’t finish processing that message before we see the message for the MouseLeave. We could fix this by just not letting you put a nested message loop there, but that would be punishing all the people who don’t care about MouseEnter/Leave in this scenario.
Actually, this issue isn’t really specific to nested message loops, they just make it worse. It’s really an example of a more general problem with synchronous events, that there is no causal ordering. Synchronous events mean that when the event happens, you process all the event handlers immediately — you don’t wait until some later time to call them. The problem is that event handlers can beget other events, and when you have multiple handlers listening, they can hear about events in a different order from when they were fired. Take a simple example where you have a single object with a single property, object.Property, and a pair of event handlers A and B listening to the property change. And suppose that A changes that same property. Consider what happens:
// default value of object.Property is 0
object.Property = 1; // initial action
call A w/ old = 0, new = 1
A does: object.Property = 2;
call A w/ old = 1, new = 2
call B w/ old = 1, new = 2
call B w/ old = 0, new = 1
So B hears about A’s changes before it hears about what A was responding to.
When you simplify these examples down, they start to seem pathological, but this sort of stuff happens in real code. Unfortunately, there’s not a whole lot we can do about it. Very early in WPF, we tried to use asynchronous events, where we would post all events to the message queue rather than process event handlers immediately. Unfortunately, that really made life difficult in 99.9% case where you didn’t have causal ordering problems — because now there’s a window between when you changed something and when the rest of the world caught up with the change. Which often forced you to post a work item of your own, which became a real pain to write and even more painful to debug. In order to fix a rare problem, we were forcing average developers doing the most basic scenarios to use advanced techniques. In other words, a very heavy-handed solution.