Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
169 views
in Technique[技术] by (71.8m points)

c# - Event raising and Read introduction in .net 4.5

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...