A common practice to avoid race conditions (in multi-threaded apps) when triggering events is this:
EventHandler<EventArgs> temp = SomeEvent;
if (temp != null) temp(e);
"Remember that delegates are immutable and this is why this technique works in theory. However, what a lot of developers don't realize is that this code could be optimized by the compiler to remove the local temp variable entirely. If this happens, this version of the code is identical to the first version, so a NullReferenceException is still possible."
The problem (according to the book) is that “this code could be optimized by the compiler to remove the local temp variable entirely. If this happens, this version of the code is identical to the first version, so a NullReferenceException is still possible”
According to CLR via C#, here is a better way to force the compiler to copy the event pointer.
virtual void OnNewMail(NewMailEventArgs e)
{
EventHandler<NewMailEventArgs> temp =
Interlocked.CompareExchange(ref NewMail, null, null);
if (temp != null)
temp(this, e);
}
Here, CompareExchange changes the NewMail reference to null if it is null and does not alter NewMail if it is not null. In other words, CompareExchange doesn’t change the value in NewMail at all, but it does return the value inside NewMail in an atomic, thread-safe way.
Richter, Jeffrey (2010-02-12). CLR via C# (p. 265). OReilly Media – A. Kindle Edition.
I am on .Net 4.0 framework, and not sure how this can possibly work, because Interlocked.CompareExchange expects a reference to a location, not a reference to a event.
Either there is an error in the book, or I misinterpreted it. Has anyone implemented this method? Or have a better way to prevent race conditions here?
UPDATE
it was my mistake, the iterlocked code works. i just had wrong casting specified, but according to Bradley (below) it is not necessary in .net 2.0 and up on windows.
The compiler (or JIT) is not allowed to optimize that
if/tempaway (in CLR 2.0 and later); the CLR 2.0 Memory Model does not allow reads from the heap to be introduced (rule #2).Thus,
MyEventcan not be read a second time; the value oftempmust be read in theifstatement.See my blog post for an extended discussion of this situation, and an explanation of why the standard pattern is fine.
However, if you are running on a non-Microsoft CLR (e.g., mono) that doesn’t provide the CLR 2.0 memory model guarantees (but only follows the ECMA memory model), or you’re running on Itanium (which has a notoriously weak hardware memory model), you will need code like Richter’s to eliminate a potential race condition.
In regards to your question about
Interlocked.CompareExchange, the syntaxpublic event EventHandler<NewMailEventArgs> NewMailis just C# syntactic sugar for declaring a private field of typeEventHandler<NewMailEventArgs>and a public event that hasaddandremovemethods. TheInterlocked.CompareExchangecall reads the value of the privateEventHandler<NewMailEventArgs>field, so this code does compile and work as Richter describes; it’s just unnecessary in the Microsoft CLR.