Quando usar volátil para neutralizar otimizações de compilador em C #

Passei um número extenso de semanas fazendo codificação multithread no C # 4.0. No entanto, há uma pergunta que permanece sem resposta para mi

Entendo que a palavra-chave volátil impede que o compilador armazene variáveis nos registros, evitando assim a leitura inadvertida de valores obsoletos. As gravações são sempre voláteis no .Net, portanto, qualquer documentação afirmando que também evita gravações em stales é redundant

Eu também sei que a otimização do compilador é um pouco "imprevisível". O código a seguir ilustrará uma paralisação devido a uma otimização do compilador (ao executar a compilação do release fora do 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
    }
}

O código se comporta conforme o esperado. No entanto, se eu descomentar o //Thread.Yield (); linha, o loop sairá.

Além disso, se eu colocar uma instrução Sleep antes do loop, ela será encerrada. Eu não entendo.

aturalmente, decorar _loop com volátil também fará com que o loop saia (no padrão mostrado

Minha pergunta é: Quais são as regras que o complier segue para determinar quando implicitamente executar uma leitura volátil? E por que ainda posso fazer o loop sair com o que considero medidas estranhas?

EDITA

IL para o código mostrado (paralisações):

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 com Yield () (não trava):

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

questionAnswers(4)

yourAnswerToTheQuestion