views:

8319

answers:

9

I frequently hear/read the following advice:

Always make a copy of an event before you check it for null and fire it. This will eliminate a potential problem with threading where the event becomes null at the location right between where you check for null and where you fire the event:

// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;

if (copy != null)
    copy(this, EventArgs.Empty); // Call any handlers on the copied list

Updated: I thought from reading about optimizations that this might also require the event member to be volatile, but Jon Skeet states in his answer that the CLR doesn't optimize away the copy.

But meanwhile, in order for this issue to even occur, another thread must have done something like this:

// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...

The actual sequence might be this mixture:

// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;

// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;    
// Good, now we can be certain that OnTheEvent will not run...

if (copy != null)
    copy(this, EventArgs.Empty); // Call any handlers on the copied list

The point being that OnTheEvent runs after the author has unsubscribed, and yet they just unsubscribed specifically to avoid that happening. Surely what is really needed is a custom event implementation with appropriate synchronisation in the add and remove accessors. And in addition there is the problem of possible deadlocks if a lock is held while an event is fired.

So is this Cargo Cult Programming? It seems that way - a lot of people must be taking this step to protect their code from multiple threads, when in reality it seems to me that events require much more care than this before they can be used as part of a multi-threaded design. Consequently, people who are not taking that additional care might as well ignore this advice - it simply isn't an issue for single-threaded programs, and in fact, given the absence of volatile in most online example code, the advice may be having no effect at all.

(And isn't it a lot simpler to just assign the empty delegate { } on the member declaration so that you never need to check for null in the first place?)

Updated: In case it wasn't clear, I did grasp the intention of the advice - to avoid a null reference exception under all circumstances. My point is that this particular null reference exception can only occur if another thread is delisting from the event, and the only reason for doing that is to ensure that no further calls will be received via that event, which clearly is NOT achieved by this technique. You'd be concealing a race condition - it would be better to reveal it! That null exception helps to detect an abuse of your component. If you want your component to be protected from abuse, you could follow the example of WPF - store the thread ID in your constructor and then throw an exception if another thread tries to interact directly with your component. Or else implement a truly thread-safe component (not an easy task).

So I contend that merely doing this copy/check idiom is cargo cult programming, adding mess and noise to your code. To actually protect against other threads requires a lot more work.

Update in response to Eric Lippert's blog posts:

So there's a major thing I'd missed about event handlers: "event handlers are required to be robust in the face of being called even after the event has been unsubscribed", and obviously therefore we only need to care about the possibility of the event delegate being null. Is that requirement on event handlers documented anywhere?

And so: "There are other ways to solve this problem; for example, initializing the handler to have an empty action that is never removed. But doing a null check is the standard pattern."

So the one remaining fragment of my question is, why is explicit-null-check the "standard pattern"? The alternative, assigning the empty delegate, requires only = delegate {} to be added to the event declaration, and this eliminates those little piles of stinky ceremony from every place where the event is raised. It would be easy to make sure that the empty delegate is cheap to instantiate. Or am I still missing something?

Surely it must be that (as Jon Skeet suggested) this is just .NET 1.x advice that hasn't died out, as it should have done in 2005?

A: 

for single threaded applicaitons, you are correc this is not an issue.

However, if you are making a component that exposes events, there is no guarantee that a consumer of your component is not going to go multithreading, in which case you need to prepare for the worst.

Using the empty delegate does solve the problem, but also causes a performance hit on every call to the event, and could possibly have GC implications.

You are right that the consumer trie dto unsubscribe in order for this to happen, but if they made it past the temp copy, then consider the message already in transit.

If you don't use the temporary variable, and don't use the empty delegate, and someone unsubscribes, you get a null reference exception, which is fatal, so I think the cost is worth it.

Jason Coyne
+22  A: 

The JIT isn't allowed to perform the optimization you're talking about in the first part, because of the condition. I know this was raised as a spectre a while ago, but it's not valid. (I checked it with either Joe Duffy or Vance Morrison a while ago; I can't remember which.)

Without the volatile modifier it's possible that the local copy taken will be out of date, but that's all. It won't cause a NullReferenceException.

And yes, there's certainly a race condition - but there always will be. Suppose we just change the code to:

TheEvent(this, EventArgs.Empty);

Now suppose that the invocation list for that delegate has 1000 entries. It's perfectly possible that the action at the start of the list will have executed before another thread unsubscribes a handler near the end of the list. However, that handler will still be executed because it'll be a new list. (Delegates are immutable.) As far as I can see this is unavoidable.

Using an empty delegate certainly avoids the nullity check, but doesn't fix the race condition. It also doesn't guarantee that you always "see" the latest value of the variable.

Jon Skeet
Is that a "yes"? :) I'm inclined to accept it as such. Do people really try to write systems in which events are dynamically enlisted/delisted from multiple threads as they are firing? (I'm mostly a GUI man on the CLR so far.) If so, I'd imagine this null reference problem is only the start of their problems.
Daniel Earwicker
It depends on exactly what you're trying to protect against. The simple form you object to gets rid of the issue of making the code go "bang!" at inconvenient moments, but that's all. Yes, it takes more effort to give a fully thread-safe event. Yes, the "delegate{}" syntax is a more convenient way of working in C# 2, if you only want basic protection. (A lot of the advice for events is probably pre-C#-2.)
Jon Skeet
Joe Duffy's "Concurrent Programming on Windows" covers the JIT optimization and memory model aspect of the question; see http://code.logos.com/blog/2008/11/events_and_threads_part_4.html
Bradley Grainger
I've accepted this based on the comment about the "standard" advice being pre-C#2, and I'm not hearing anyone contradicting that. Unless it is really expensive to instantiate your event args, just put '= delegate {}' on the end of your event declaration and then call your events directly as if they are methods; never assign null to them. (The other stuff I brought in about ensuring a handler is not called after delisting, that was all irrelevant, and is impossible to ensure, even for single threaded code, e.g. if handler 1 asks handler 2 to delist, handler 2 will still get called next.)
Daniel Earwicker
The only problem case (as always) is structs, where you cannot ensure that they will be instantiated with anything other than null values in their members. But structs suck.
Daniel Earwicker
+12  A: 

I see a lot of people going toward the extension method of doing this ...

public static class Extensions   
{   
  public static void Raise<T>(this EventHandler<T> handler, 
    object sender, T args) where T : EventArgs   
  {   
    if (handler != null) handler(sender, args);   
  }   
}

That gives you nicer syntax to raise the event ...

MyEvent.Raise( this, new MyEventArgs() );

And also does away with the local copy since it is captured at method call time.

JP Alioto
Nice syntax! Im going to start using that right away.
Jason Coyne
I like the syntax, but let's be clear... it doesn't solve the problem with a stale handler getting invoked even after it has been unregistered. This *only* solves the null dereference problem.While I like the syntax, I question whether it is really any better than:public event EventHandler<T> MyEvent = delete {};...MyEvent (this, new MyEventArgs());This is also a very low-friction solution that I like for its simplicity.
Simon Gillbee
@Simon I see different people saying different things on this. I've tested it and what I've done indicates to me that this does handle the null handler issue. Even if the original Sink unregisters from the Event after the handler != null check, the Event is still raised and no exception is thrown.
JP Alioto
yup, cf this question: http://stackoverflow.com/questions/192980/boiler-plate-code-replacement-is-there-anything-bad-about-this-code
Benjol
Can I get verification that this method is indeed thread safe without using implicit typing?
SwDevMan81
A: 

This practice is not about enforcing a certain order of operations. It's actually about avoiding a null reference exception.

The reasoning behind people caring about the null reference exception and not the race condition would require some deep psychological research. I think it has something to do with the fact that fixing the null reference problem is much easier. Once that is fixed, they hang a big "Mission Accomplished" banner on their code and unzip their flight suit.

Note: fixing the race condition probably involves using a synchronous flag track whether the handler should run

dss539
I'm not asking for a solution to that problem. I'm wondering why there is widespread advice to spray extra code-mess around event firing, when it only avoids a null exception when there is a hard-to-detect race condition, which will still exist.
Daniel Earwicker
Well that was my point. They don't *care* about the race condition. They only care about the null reference exception. I will edit that into my answer.
dss539
And my point is: why would it make sense to care about the null reference exception and yet *not* care about the race condition?
Daniel Earwicker
"I think it has something to do with the fact that fixing the null reference problem is much easier."
dss539
A: 

I've never really considered this to be much of an issue because I generally only protect against this sort of potential threading badness in static methods (etc) on my reusable components, and I don't make static events.

Am I doing it wrong?

Greg D
If you allocate an instance of a class with mutable state (fields that change their values) and then let several threads access the same instance simultaneously, without using locking to protect those fields from being modified by two threads at the same time, you're probably doing it wrong. If all your threads have their own separate instances (sharing nothing) or all your objects are immutable (once allocated, their fields' values never change) then you're probably okay.
Daniel Earwicker
My general approach is to leave synchronization up to the caller except in static methods. If I'm the caller, then I'll synchronize at that higher level. (with the exception of an object whose sole purpose is handling synchronized access, of course. :) )
Greg D
+33  A: 

This question came up in an internal discussion a while back; I've been intending to blog this for some time now.

My post on the subject is here:

http://blogs.msdn.com/ericlippert/archive/2009/04/29/events-and-races.aspx

Eric Lippert
Welcome Eric, everyone :)
VVS
Thanks Eric. As the comment facility here is a little limited, I've put my remaining question-fragments in an update to the question above.
Daniel Earwicker
A: 

So I'm a little late to the party here. :)

As for the use of null rather than the null object pattern to represent events with no subscribers, consider this scenario. You need to invoke an event, but constructing the object (EventArgs) is non-trivial, and in the common case your event has no subscribers. It would be beneficial to you if you could optimize your code to check to see if you had any subscribers at all before you committed processing effort to constructing the arguments and invoking the event.

With this in mind, a solution is to say "well, zero subscribers is represented by null." Then simply perform the null check before performing your expensive operation. I suppose another way of doing this would have been to have a Count property on the Delegate type, so you'd only perform the expensive operation if myDelegate.Count > 0. Using a Count property is a somewhat nice pattern that solves the original problem of allowing optimization, and it also has the nice property of being able to be invoked without causing a NullReferenceException.

Keep in mind, though, that since delegates are reference types, they are allowed to be null. Perhaps there was simply no good way of hiding this fact under the covers and supporting only the null object pattern for events, so the alternative may have been forcing developers to check both for null and for zero subscribers. That would be even uglier than the current situation.

Note: This is pure speculation. I'm not involved with the .NET languages or CLR.

Levi
I assume you mean "the use of empty delegate rather than..." You can already do what you suggest, with an event initialized to the empty delegate. The test (MyEvent.GetInvocationList().Length == 1) will be true if the initial empty delegate is the only thing on the list. There still would be no need to make a copy first. Though I think the case you describe would be extremely rare anyway.
Daniel Earwicker
I think we're conflating the ideas of delegates and events here. If I have an event Foo on my class, then when external users call MyType.Foo += / -=, they're actually calling add_Foo() and remove_Foo() methods. However, when I reference Foo from within the class where it's defined, I'm actually referencing the underlying delegate directly, not the add_Foo() and remove_Foo() methods. And with the existence of types like EventHandlerList, there's nothing mandating that the delegate and the event even be in the same place. This is what I meant by the "Keep in mind" paragraph in my response.
Levi
(cont.) I admit that this is a confusing design, but the alternative may have been worse. Since in the end all you have is a delegate - you might reference the underlying delegate directly, you might get it from a collection, you might instantiate it on the fly - it may have been technically infeasible to support anything other than the "check for null" pattern.
Levi
As we're talking about firing the event, I can't see why the add/remove accessors are important here.
Daniel Earwicker
+14  A: 

"Why is explicit-null-check the 'standard pattern'?"

I suspect the reason for this might be that the null-check is more performant.

If you always subscribe an empty delegate to your events when they are created, there will be some overheads:

  • Cost of constructing the empty delegate.
  • Cost of constructing a delegate chain to contain it.
  • Cost of invoking the pointless delegate every single time the event is raised.

(Note that UI controls often have a large number of events, most of which are never subscribed to. Having to create a dummy subscriber to each event and then invoke it would likely be a significant performance hit.)

I did some cursory performance testing to see the impact of the subscribe-empty-delegate approach, and here are my results:

Executing 50000000 iterations . . .
OnNonThreadSafeEvent took:      432ms
OnClassicNullCheckedEvent took: 490ms
OnPreInitializedEvent took:     614ms <--
Subscribing an empty delegate to each event . . .
Executing 50000000 iterations . . .
OnNonThreadSafeEvent took:      674ms
OnClassicNullCheckedEvent took: 674ms
OnPreInitializedEvent took:     2041ms <--
Subscribing another empty delegate to each event . . .
Executing 50000000 iterations . . .
OnNonThreadSafeEvent took:      2011ms
OnClassicNullCheckedEvent took: 2061ms
OnPreInitializedEvent took:     2246ms <--
Done

Note that for the case of zero or one subscribers (common for UI controls, where events are plentiful), the event pre-initialised with an empty delegate is notably slower (over 50 million iterations...)

For more information and source code, visit this blog post on .NET Event invocation thread safety that I published just the day before this question was asked (!)

(My test set-up may be flawed so feel free to download the source code and inspect it yourself. Any feedback is much appreciated.)

Daniel Fortunov
I think you make the key point in your blog post: there is no need to worry about the performance implications until it is a bottleneck. Why let the ugly way be the recommended way? If we wanted premature optimisation instead of clarity, we'd be using assembler - so my question remains, and I think the likely answer is that the advice simply predates anonymous delegates and it takes a long time for human culture to shift old advice, like in the famous "pot roast story".
Daniel Earwicker
And your figures prove the point very well: the overhead comes down to just two and a half NANOSECONDS(!!!) per event raised (pre-init vs. classic-null). This would be undetectable in almost apps with real work to do, but given that the vast majority of event usage is in GUI frameworks, you'd have to compare this with the cost of repainting parts of a screen in Winforms, etc., so it becomes even more invisible in the deluge of real CPU work and waiting around for resources. You get +1 from me for the hard work, anyway. :)
Daniel Earwicker
A: 

Wire all your events at construction and leave them alone. The design of the Delegate class cannot possibly handle any other usage correctly, as I will explain in the final paragraph of this post.

First of all, there's no point in trying to intercept an event notification when your event handlers must already make a synchronized decision about whether/how to respond to the notification.

Anything that may be notified, should be notified. If your event handlers are properly handling the notifications (i.e. they have access to an authoritative application state and respond only when appropriate), then it will be fine to notify them at any time and trust they will respond properly.

The only time a handler shouldn't be notified that an event has occurred, is if the event in fact hasn't occurred! So if you don't want a handler to be notified, stop generating the events (i.e. disable the control or whatever is responsible for detecting and bringing the event into existence in the first place).

Honestly, I think the Delegate class is unsalvageable. The merger/transition to a MulticastDelegate was a huge mistake, because it effectively changed the (useful) definition of an event from something that happens at a single instant in time, to something that happens over a timespan. Such a change requires a synchronization mechanism that can logically collapse it back into a single instant, but the MulticastDelegate lacks any such mechanism. Synchronization should encompass the entire timespan or instant the event takes place, so that once an application makes the synchronized decision to begin handling an event, it finishes handling it completely (transactionally). With the black box that is the MulticastDelegate/Delegate hybrid class, this is near impossible, so adhere to using a single-subscriber and/or implement your own kind of MulticastDelegate that has a synchronization handle that can be taken out while the handler chain is being used/modified. I'm recommending this, because the alternative would be to implement synchronization/transactional-integrity redundantly in all your handlers, which would be ridiculously/unnecessarily complex.

Triynko
[1] There is no useful event handler that happens at "a single instant in time". All operations have a timespan. Any single handler can have a non-trivial sequence of steps to perform. Supporting a list of handlers changes nothing.
Daniel Earwicker
[2] Holding a lock while an an event is fired is total madness. It leads inevitably to deadlock. The source takes out lock A, fires event, the sink takes out lock B, now two locks are held. What if some operation in another thread results in the locks being taken out in the reverse order? How can such deadly combinations be ruled out when responsibility for locking is divided between separately designed/tested components (which is the whole point of events)?
Daniel Earwicker
[3] None of these issues in any way reduce the all-pervasive usefulness of normal multicast delegates/events in single-threaded composition of components, especially in GUI frameworks. This use case covers the vast majority of uses of events. Using events in a free-threaded way is of questionable value; this does not in any way invalidate their design or their obvious usefulness in the contexts where they make sense.
Daniel Earwicker
[4] Threads + synchronous events is essentially a red herring. Queued asynchronous communication is the way to go.
Daniel Earwicker
[1] I wasn't referring to measured time...I was talking about atomic operations, which logically occur instantaneously... and by that I mean nothing else involving the same resources they're using can change while the event is happening because it's serialized with a lock.
Triynko
[2] For an event to be fired, a thread must be running that reads values (mouse_xy, time, status, etc.) and then calls an event handler chain. If, before calling that event chain, a lock on the event chain is taken out, another thread cannot modify the event chain. If another thread wants to modify the event chain, it has to wait for the event to complete. This simply prevents disturbances to the event chain, and ensures all handlers complete instantaneously from the perspective of anything wanting to modify the event chain.
Triynko
[3] In a single-threaded application, MulticastDelegate (MCD) is fine, because calling a MCD with handlers A,B, and C, results in A,B, and C called in order, without disruption (i.e. instantaneously from the perspective of other code in the thread). But in multi-threaded apps, assumptions held just before calling the same MCD no longer hold. With no lock, the authoritative event chain may change while the immutable original is in the middle of calling events in the chain; hence the confusion when updating the original chain doesn't produce the desired effect. The convention is misleading.
Triynko
[4] In order for anything that runs in parallel to produce any kind of coherent result, synchronization must occur. In the brain, we call it consciousness. Synchronization must occur, otherwise you just have a set of unrelated things performing unrelated tasks.
Triynko
Avoiding deadlock is easy anyway, just don't assume the "event generating and processing" thread is single-threaded; don't assume it's never blocked. Problems appear when single-threaded assumptions enter a multi-threaded design.
Triynko
Given your comment [3], it sounds like you generally agree with me. Events/MCD are fine for single-threaded, not much use for multi-threaded. But in the specifics, there are problems. The idea that handlers A, B, C happen "instantaneously" from the perspective of "other code in the thread" - what does that mean? They execute sequentially, and each can see partial changes in state caused by the ones executing before. Also, anyone of of them can replace the event chain during this, leading to a single-threaded version of exactly the same issues.
Daniel Earwicker
"Avoiding deadlock is easy..." Oh dear.
Daniel Earwicker
Yep, agreement on [3]. Executing "instantaneously" from the perspective of "other code in the thread" means that the MCD call (to all the handlers) completes without "timing"-related side effects from external (other thread) activity. The handlers in the MCD chain can do whatever they want but that's designed into the handlers and into the call as a whole, and would not be considered external or time-sensitive activity. Since there are no timing issues during the call, time loses all meaning so the call can be said to occur instantaneously from outside (before/after) the call to the MCD.
Triynko
Deadlock is easy to avoid, because its easy to understand and create. Spawn two threads and join them to each other -- each waiting for the other to complete... [something]. To avoid deadlock, just make sure you don't (assume/depend on/write code as-if) another thread that shares resources with you won't ever be blocked. For example, don't ask someone to run a marathon for you in the same breath you ask to borrow their shoes.
Triynko
@Triynko - you're looking it at a problem that occurs purely because of a locally recognisable contradiction. Those deadlocks are easy to spot. What is harder is when they arise due to a combination of two separate modules, each of which looks perfectly correct and harmless by itself. Holding a lock while firing an event is inviting such combinatory problems to arise. To run a marathon I need both shoes, which I share with my neighbour. He acquires the left shoe, I acquire the right shoe, and then we both wait for eternity for the other shoe to become available.
Daniel Earwicker
@Earwicker - Code location doesn't change the condition under which deadlock occurs; therefore, the difficulty in spotting it is the same, but more files are involved. Identify threads first - the basic unit of concurrency - and code they may execute. Enumerate the resources each thread's code locks. Deadlock is possible if and only if the sets intersect (i.e. they share resources). If the sets intersect AND either thread asks the other to complete an operation *involving a shared resource*, then deadlock is probable; otherwise, it cannot occur. Tracking your threads and resources works.
Triynko