Cuando se usa volátil para contrarrestar las optimizaciones del compilador en C #

He pasado una gran cantidad de semanas haciendo codificación multiproceso en C # 4.0. Sin embargo, hay una pregunta que sigue sin respuesta para mí.

Entiendo que la palabra clave volátil evita que el compilador almacene variables en registros, evitando así leer inadvertidamente valores obsoletos. Las escrituras siempre son volátiles en .Net, por lo que cualquier documentación que indique que también evita escrituras obsoletas es redundante.

También sé que la optimización del compilador es algo "impredecible". El siguiente código ilustrará un bloqueo debido a una optimización del compilador (cuando se ejecuta la compilación de lanzamiento fuera de VS):

class Test
{
    public struct Data
    {
        public int _loop;
    }

    public static Data data;

    public static void Main()
    {
        data._loop = 1;
        Test test1 = new Test();

        new Thread(() =>
        {
            data._loop = 0;
        }
        ).Start();

        do
        {
            if (data._loop != 1)
            {
                break;
            }

            //Thread.Yield();
        } while (true);

        // will never terminate
    }
}

El código se comporta como se esperaba. Sin embargo, si elimino el //Thread.Yield (); línea, entonces el bucle saldrá.

Además, si pongo una instrucción Sleep antes del ciclo do, se cerrará. No lo entiendo.

Naturalmente, decorar _loop con volátil también hará que el bucle salga (en el patrón que se muestra).

Mi pregunta es: ¿Cuáles son las reglas que sigue el cumplidor para determinar cuándo realizar una lectura volátil? ¿Y por qué todavía puedo obtener el ciclo para salir con lo que considero que son medidas extrañas?

EDITA

IL para el código como se muestra (puestos):

L_0038: ldsflda valuetype ConsoleApplication1.Test/Data ConsoleApplication1.Test::data
L_003d: ldfld int32 ConsoleApplication1.Test/Data::_loop
L_0042: ldc.i4.1 
L_0043: beq.s L_0038
L_0045: ret 

IL con rendimiento () (no se detiene):

L_0038: ldsflda valuetype ConsoleApplication1.Test/Data ConsoleApplication1.Test::data
L_003d: ldfld int32 ConsoleApplication1.Test/Data::_loop
L_0042: ldc.i4.1 
L_0043: beq.s L_0046
L_0045: ret 
L_0046: call bool [mscorlib]System.Threading.Thread::Yield()
L_004b: pop 
L_004c: br.s L_0038

Respuestas a la pregunta(8)

Su respuesta a la pregunta