Je suppose que l'optimiseur est dupé par l'absence de mot-clé «volatile» sur la isCompletevariable.
Bien sûr, vous ne pouvez pas l'ajouter, car c'est une variable locale. Et bien sûr, puisqu'il s'agit d'une variable locale, elle ne devrait pas être du tout nécessaire, car les locaux sont conservés sur pile et ils sont naturellement toujours «frais».
Cependant , après compilation, ce n'est plus une variable locale . Puisqu'il est accédé dans un délégué anonyme, le code est divisé, et il est traduit en une classe d'assistance et un champ de membre, quelque chose comme:
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;
}
}
J'imagine maintenant que le compilateur / optimiseur JIT dans un environnement multithread, lors du traitement de la TheHelperclasse, peut réellement mettre en cache la valeur falsedans un registre ou un cadre de pile au début de la Body()méthode, et ne jamais l'actualiser jusqu'à la fin de la méthode. C'est parce qu'il n'y a AUCUNE GARANTIE que le thread et la méthode ne se termineront PAS avant que "= true" ne soit exécuté, donc s'il n'y a aucune garantie, alors pourquoi ne pas le mettre en cache et obtenir l'amélioration des performances de la lecture de l'objet de tas une fois au lieu de le lire à chaque itération.
C'est exactement pourquoi le mot-clé volatileexiste.
Pour cette classe d'aide pour être correct un peu petit peu mieux 1) dans des environnements multi-thread, il doit avoir:
public volatile bool isComplete = false;
mais, bien sûr, puisqu'il s'agit de code généré automatiquement, vous ne pouvez pas l'ajouter. Une meilleure approche serait d'ajouter des lock()s autour des lectures et des écritures isCompleted, ou d'utiliser d'autres utilitaires de synchronisation ou de threading / tâches prêts à l'emploi au lieu d'essayer de le faire sans système d'exploitation (ce qui ne sera pas du métal nu, puisque c'est C # sur CLR avec GC, JIT et (..)).
La différence en mode débogage se produit probablement parce qu'en mode débogage de nombreuses optimisations sont exclues, vous pouvez donc déboguer le code que vous voyez à l'écran. Par conséquent, il while (!isComplete)n'est pas optimisé afin que vous puissiez y définir un point d'arrêt, et isCompleten'est donc pas mis en cache de manière agressive dans un registre ou une pile au début de la méthode et est lu à partir de l'objet sur le tas à chaque itération de boucle.
BTW. Ce sont juste mes suppositions à ce sujet. Je n'ai même pas essayé de le compiler.
BTW. Cela ne semble pas être un bug; c'est plus comme un effet secondaire très obscur. De plus, si j'ai raison, cela peut être un manque de langage - C # devrait permettre de placer un mot-clé «volatile» sur les variables locales qui sont capturées et promues dans les champs membres dans les fermetures.
1) voir ci-dessous pour un commentaire d'Eric Lippert sur volatileet / ou cet article très intéressant montrant les niveaux de complexité impliqués pour s'assurer que le code reposant sur volatileest sûr ... euh, bien ... euh, disons OK.