Supongo que el optimizador es engañado por la falta de palabra clave 'volátil' en la isCompletevariable.
Por supuesto, no puede agregarlo, porque es una variable local. Y, por supuesto, dado que es una variable local, no debería ser necesaria en absoluto, porque los locales se mantienen en la pila y, naturalmente, siempre están "frescos".
Sin embargo , después de compilar, ya no es una variable local . Dado que se accede a él en un delegado anónimo, el código se divide y se traduce en una clase auxiliar y un campo de miembro, 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;
}
}
Puedo imaginar ahora que el compilador / optimizador JIT en un entorno multiproceso, al procesar la TheHelperclase, puede almacenar en caché el valor falseen algún registro o marco de pila al comienzo del Body()método, y nunca actualizarlo hasta que finalice el método. Esto se debe a que NO HAY GARANTÍA de que el hilo y el método NO terminen antes de que se ejecute "= true", así que si no hay garantía, ¿por qué no almacenarlo en caché y obtener el aumento de rendimiento de leer el objeto del montón una vez en lugar de leerlo cada vez? iteración.
Esta es exactamente la razón por la que volatileexiste la palabra clave .
Para que esta clase auxiliar sea correcta un poquito mejor 1) en entornos de subprocesos múltiples, debería tener:
public volatile bool isComplete = false;
pero, por supuesto, dado que es un código generado automáticamente, no puede agregarlo. Un mejor enfoque sería agregar algunos lock()mensajes de correo electrónico alrededor de lecturas y escrituras isCompleted, o usar alguna otra sincronización lista para usar o utilidades de subprocesamiento / tareas en lugar de intentar hacerlo de forma completa (que no será completa , ya que es C # en CLR con GC, JIT y (..)).
La diferencia en el modo de depuración se produce probablemente porque en el modo de depuración se excluyen muchas optimizaciones, por lo que puede, bueno, depurar el código que ve en la pantalla. Por while (!isComplete)lo tanto, no está optimizado para que pueda establecer un punto de interrupción allí y, por isCompletelo tanto, no se almacena en caché de manera agresiva en un registro o pila al inicio del método y se lee del objeto en el montón en cada iteración del ciclo.
Por cierto. Eso es solo mis conjeturas sobre eso. Ni siquiera intenté compilarlo.
Por cierto. No parece ser un error; es más como un efecto secundario muy oscuro. Además, si estoy en lo cierto, entonces puede ser una deficiencia de lenguaje: C # debería permitir colocar una palabra clave 'volátil' en las variables locales que se capturan y promueven a los campos de miembros en los cierres.
1) vea a continuación los comentarios de Eric Lippert sobre volatiley / o este artículo muy interesante que muestra los niveles de complejidad involucrados en garantizar que el código en el que se basa volatilesea seguro ... uh, bien ... uh, digamos que está bien.