Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread Safe Event Calls

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.

like image 341
Sonic Soul Avatar asked Jun 22 '12 15:06

Sonic Soul


People also ask

Are .NET events thread-safe?

If you mean the += then yes: it is thread-safe. The specification requires that the accessors for field-like events are thread-safe, although the implementation can vary (for example, the MS compiler used to use lock(this) or lock(typeof(DeclaringType)) , however it now uses Interlocked instead).

Which objects are thread-safe?

Usually, objects that are read-only are thread-safe. Many objects that are not read-only are able to have data accesses (read-only) occur with multiple threads without issue, if the object is not modified in the middle.

Do events run on separate threads?

Answers. 1) No. The event handlers are run off the timer tick event as well as the swapping of thread execution.


2 Answers

The compiler (or JIT) is not allowed to optimize that if/temp away (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, MyEvent can not be read a second time; the value of temp must be read in the if statement.

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 syntax public event EventHandler<NewMailEventArgs> NewMail is just C# syntactic sugar for declaring a private field of type EventHandler<NewMailEventArgs> and a public event that has add and remove methods. The Interlocked.CompareExchange call reads the value of the private EventHandler<NewMailEventArgs> field, so this code does compile and work as Richter describes; it's just unnecessary in the Microsoft CLR.

like image 162
Bradley Grainger Avatar answered Sep 29 '22 05:09

Bradley Grainger


Now this is only a partial answer to your question, because I can not comment on using Interlocked.CompareExchange, however I think this information could be useful.

The problem is that the compiler may optimize that if/temp away,

Well, according to CLR via C# (p. 264–265)

[The] code could be optimized by the compiler to remove the local […] variable entirely. If this happens, this version of the code is identical to the [version that references the event twice], so a NullReferenceException is still possible.

So, it is possible, however, it important to know that Microsoft’s just-in-time (JIT) compiler does not ever optimize away the local variable. While this could possibly change, it is unlikely, because it would probably break a lot of applications.

This is because .Net has a strong memory model: http://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S5

Reads and writes cannot be introduced.

and

The model does not allow reads to be introduced, however, because this would imply refetching a value from memory, and in low-lock code memory could be changing.

 

However, Mono, which follows a much weaker memory model, could optimize that local variable away.

Bottom line: Unless you're planning on using Mono, don't worry about it.

And even then, that behavior can be suppressed with volatile declarations.

like image 31
caesay Avatar answered Sep 29 '22 05:09

caesay