Eu acho que o otimizador é enganado pela falta da palavra-chave 'volátil' na isCompletevariável.
Claro, você não pode adicioná-lo, porque é uma variável local. E, claro, como é uma variável local, não deveria ser necessária, porque os locais são mantidos na pilha e, naturalmente, estão sempre "atualizados".
No entanto , após a compilação, não é mais uma variável local . Como é acessado em um delegado anônimo, o código é dividido e traduzido em uma classe auxiliar e campo de membro, algo como:
public static void Main(string[] args)
{
TheHelper hlp = new TheHelper();
var t = new Thread(hlp.Body);
t.Start();
Thread.Sleep(500);
hlp.isComplete = true;
t.Join();
Console.WriteLine("complete!");
}
private class TheHelper
{
public bool isComplete = false;
public void Body()
{
int i = 0;
while (!isComplete) i += 0;
}
}
Posso imaginar agora que o compilador / otimizador JIT em um ambiente multithread, ao processar a TheHelperclasse, pode realmente armazenarfalse em cache o valor em algum registro ou quadro de pilha no início do Body()método e nunca atualizá-lo até que o método termine. Isso porque não há NENHUMA GARANTIA de que o thread e o método NÃO terminarão antes que "= true" seja executado, então, se não houver garantia, por que não armazená-lo em cache e obter o aumento de desempenho ao ler o objeto heap uma vez em vez de lê-lo a cada iteração.
É exatamente por isso que a palavra-chave volatileexiste.
Para que esta classe auxiliar seja um pouco melhor corrigida 1) em ambientes multi-threaded, ela deve ter:
public volatile bool isComplete = false;
mas, é claro, como é um código gerado automaticamente, você não pode adicioná-lo. Uma abordagem melhor seria adicionar alguns programas em lock()torno de leituras e gravações isCompleted, ou usar algum outro utilitário de sincronização ou threading / tasking pronto para usar, em vez de tentar fazê-lo bare-metal (o que não será bare-metal, já que é C # no CLR com GC, JIT e (..)).
A diferença no modo de depuração ocorre provavelmente porque no modo de depuração muitas otimizações são excluídas, então você pode, bem, depurar o código que você vê na tela. Portanto, while (!isComplete)não é otimizado para que você possa definir um ponto de interrupção lá e, portanto, isCompletenão é agressivamente armazenado em cache em um registro ou pilha no início do método e é lido do objeto no heap a cada iteração do loop.
BTW. Isso é apenas meu palpite sobre isso. Eu nem tentei compilá-lo.
BTW. Não parece ser um bug; é mais como um efeito colateral muito obscuro. Além disso, se eu estiver certo sobre isso, pode ser uma deficiência de linguagem - C # deve permitir colocar a palavra-chave 'volátil' em variáveis locais que são capturadas e promovidas a campos de membro nos fechamentos.
1) veja abaixo os comentários de Eric Lippert sobre volatilee / ou este artigo muito interessante que mostra os níveis de complexidade envolvidos em garantir que o código que confia volatileseja seguro ... uh, bom ... uh, digamos OK.