It is known that, unlike Java\'s volatiles, .NET\'s ones allow reordering of volatile writes with the following volatile reads from another location. When it is a problem
Here I will use an arrow notation to conceptualize the memory barriers. I use an up arrow ↑ and a down arrow ↓ to represent volatile writes and reads respectively. Think of the arrow head as pushing away any other reads or writes. So no other memory access can move past the arrow head, but they can move past the tail.
Consider your first example. This is how it would be conceptualized.
↑
volatile1 write // A
volatile2 read // B
↓
So clearly we can see that the read and the write are allowed to switch positions. You are correct.
Now consider your second example. You claimed that introducing a dummy read would prevent the write of A and the read of B from getting swapped.
↑
volatile1 write // A
volatile1 read // A
↓
volatile2 read // B
↓
We can see that B is prevented from floating up by the dummy read of A. We can also see that the read of A cannot float down because, by inference, that would be the same as B moving up before A. But, notice that we have no ↑ arrow that would prevent the write to A from floating down (remember it can still move past the tail of an arrow). So no, at least theoretically, injecting a dummy read of A will not prevent the original write of A and the read of B from getting swapped because the write to A is still allowed to move downward.
I had to really think about this scenario. One thing I pondered for a quite some time is whether the read and write to A are locked together in tandem. If so then that would prevent the write to A from moving down because it would have to take the read with it which we already said was prevented. So if you go with that school of thought then your solution might just work. But, I read the specification again and I see nothing special mentioned about volatile accesses to the same variable. Obviously, the thread has to execute in a manner that is logically consistent with the original program sequence (that is mentioned in the specification). But, I can visualize ways the compiler or hardware could optimize (or otherwise reorder) that tandem access of A and still get the same result. So, I simply have to side with caution here and assume that the write to A can move down. Remember, a volatile read does not mean "fresh read from main memory". The write to A could be cached in a register and then the read comes from that register delaying the actual write to a later time. Volatile semantics do not prevent that scenario as far as I know.
The correct solution would be to put a call to Thread.MemoryBarrier in between the accesses. You can see how this is conceptualized with the arrow notation.
↑
volatile1 write // A
↑
Thread.MemoryBarrier
↓
volatile2 read // B
↓
Now you can see that the read is not allowed to float up and the write is not allowed to float down preventing the swap.
You can see some of my other memory barrier answers using this arrow notation here, here, and here just to name a few.