2

I have the question related to this MSDN Magazine article.

Read Introduction As I just explained, the compiler sometimes fuses multiple reads into one. The compiler can also split a single read into multiple reads. In the .NET Framework 4.5, read introduction is much less common than read elimination and occurs only in very rare, specific circumstances. However, it does sometimes happen.

public class ReadIntro {
  private Object _obj = new Object();
  void PrintObj() {
    Object obj = _obj;
    if (obj != null) {
      Console.WriteLine(obj.ToString());
    // May throw a NullReferenceException
    }
  }
  void Uninitialize() {
    _obj = null;
  }
}

If you examine the PrintObj method, it looks like the obj value will never be null in the obj.ToString expression. However, that line of code could in fact throw a NullReferenceException. The CLR JIT might compile the PrintObj method as if it were written like this:

void PrintObj() {
  if (_obj != null) {
    Console.WriteLine(_obj.ToString());
  }
}

But isn't it a pattern of working with events?!

void RaiseEvent()
{
    var myEvent = MyEvent;
    if (myEvent != null)
    {
         myEvent(this, EventArgs.Empty);
    }
}

Do I miss something important here?

Pavel Voronin
  • 13,503
  • 7
  • 71
  • 137
  • 1
    [this question](http://stackoverflow.com/questions/14799876/read-introduction-in-c-sharp-how-to-protect-against-it) covers similar ground, points out that, in normal usage, you're unlikely to actually encounter the issue (since the JIT team are aware of this pattern, they're unlikely to introduce the optimization and break it), and there's one [answer](http://stackoverflow.com/a/14800390/15498) which shows a way to prevent it occurring. – Damien_The_Unbeliever Apr 23 '13 at 07:07
  • These are scary things that go bump in the night. It is not like the non-optimized version of that code is trouble-free either. You are still firing an event that the listener might have already un-subscribed. This won't often come to a good end either. The best guidance is the simple one: don't do it. – Hans Passant Apr 23 '13 at 11:17

1 Answers1

5

This article got me confused as well and I did some research. I found two schools of thought.

1. Some say the pattern is safe

Because the CLR 2.0 memory model was more strict than 1.x and prohibits it.

"Reads and writes cannot be introduced", MSDN Magazine (Oct 05), article Understand the Impact of Low-Lock Techniques in Multithreaded Apps.

"the .NET memory model prohibits it [read introduction] for ordinary variables referring to GC heap memory", Joe Duffy, book Concurrent Programming on Windows, pp517-8.

[Note: Joe Duffy basically says the same thing but leaves the possibility of read introductions on the stack, which is not shared hence safe]

I find those ".NET 2.0 memory model" explanations strange. I have read the 2012 ECMA CLI specification as well as the C# standard and found no reference to a statement prohibiting read introduction. It's unlikely that they weakened the memory model between 2.0 and 4. (??)

On the other hand, I believe that the JIT team is aware of those patterns and will not break them, at least on x86... but saying this is not the same as saying that it's in the standard. The team decision may change in the future or on other platforms.

EDIT Don't miss Eric Lippert's comment below: "no read introduction" is a promise of Microsoft CLI implementation. There's nothing about it in the ECMA standard and when using other implementations (e.g. Mono) all bets are off. END EDIT

2. Some say it's unsafe

Specifically: Igor Ostrovsky in the article you've quoted, as well as Stephen Toub in a discussion inside the comments of this blog post: http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx.

Basically they say that read introduction or elimination is a common compiler optimization that both the C# and the JIT are allowed to do if they don't change the single-threaded behavior.

[Note: Eric Lippert has said that the C# compiler is not doing such optimizations at the moment.]

Note that Igor seems to be aware that the JIT is quite conservative and explicitely points out in the article that your sample code will not break in .NET 4.5 on x86-x64. On the other hand he says that it may break in other cases, without precising if it's more complex code patterns, future or past .net releases, or other platforms.

Solution

If you want to be 100% safe, the solution is to use a volatile read. Volatile reads and writes are defined as side-effects by the C# standard, hence they can't be introduced nor removed.

The ECMA CLI standard has a similar explicit statement about not removing volatile reads and writes.

A note about thread-safe events

As many have pointed out, there is more to thread-safety than just the event raising code. Your event handler should be ready to be called after it has unsubscribed.

I agree with Hans Passant that the best guidance is "don't do it", but sometimes you need to. In those cases just be sure that your event handler code is also thread safe. In those cases you might also want to consider a simpler lock-based approach to synchronisation.

jods
  • 4,581
  • 16
  • 20
  • To address your point about the ECMA CLI spec and the C# spec: the stronger memory model promises made by CLR 2.0 are promises made by *Microsoft*. A third party that decided to make their own implementation of C# that generates code that runs on their own implementation of CLI could choose a weaker memory model and still be compliant with the specifications. Whether the Mono team has done so, I do not know; you'll have to ask them. – Eric Lippert May 28 '13 at 15:52
  • Thanks for the precision Eric! Because this seems to be undocumented (at least "officially"), it begs the question: is it a _reliable_ promise? I.e. if I only target MS .net can I assume that read and write introductions will never happen, on any Platform and any future release? – jods May 29 '13 at 19:31
  • hey, I couldn't make that kind of promise even when I *did* work for Microsoft! I have heard rumours to the effect that the CLR running on weak memory model hardware like the Surface device does generate code that imposes a stronger memory model but who knows what will happen in the future as hardware continues to evolve? You should pose your question to someone who actually has the ability to answer it definitively. – Eric Lippert May 29 '13 at 19:44