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.