我猜想优化器被isComplete变量上缺少'volatile'关键字愚弄了。
当然,您不能添加它,因为它是局部变量。当然,由于它是一个局部变量,因此根本就不需要它,因为局部变量保留在堆栈中,并且自然总是“新鲜”的。
但是,编译后,它不再是局部变量。由于它是在匿名委托中访问的,因此代码被拆分,并被翻译为帮助器类和成员字段,如下所示:
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;
}
}
我现在可以想象,在多线程环境中的JIT编译器/优化器在处理TheHelper类时,实际上可以在方法开始时将其值缓存false在某个寄存器或堆栈帧中Body(),而在方法结束之前永远不刷新它。那是因为没有保证在执行“ = true”之前线程和方法不会结束,所以如果没有保证,那为什么不缓存它并获得一次读取堆对象而不是每次读取它的性能提升呢?迭代。
这正是关键字volatile存在的原因。
为了使这个帮助器类正确 一些, 1)在多线程环境中,它应该具有:
public volatile bool isComplete = false;
但是,由于它是自动生成的代码,因此您不能添加它。更好的方法是在上添加一些lock()读写操作isCompleted,或者使用其他一些现成的同步或线程/任务实用程序,而不是尝试使用裸机(它不会是裸机,因为它是CLR上的C#,带有GC,JIT和(..))。
调试模式的不同可能是因为在调试模式下没有进行许多优化,因此您可以调试屏幕上看到的代码。因此while (!isComplete)未进行优化,因此您可以在此处设置一个断点,因此isComplete不会在方法启动时主动将其缓存在寄存器或堆栈中,并且在每次循环迭代时都从堆上的对象中读取该值。
顺便说一句。那只是我的猜测。我什至没有尝试编译它。
顺便说一句。看来这不是一个错误;这更像是一个非常晦涩的副作用。另外,如果我是对的,那么这可能是一种语言缺陷-C#应该允许将'volatile'关键字放在捕获并提升为闭包中成员字段的局部变量上。
1)请参阅下面的Eric Lippert关于volatile和/或这篇非常有趣的文章的评论,该文章显示了确保代码所依赖volatile的安全性 ..uh,良好 ..uh所涉及的复杂程度。