我猜测优化器被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;
}
}
我现在可以想象,在多线程环境下,当处理TheHelper类时,JIT编译器/优化器实际上可以在Body()方法开始时将值false缓存在某个寄存器或堆栈帧中,并且直到该方法结束前都不会刷新它。这是因为无法保证线程和方法在执行“=true”之前不会结束,所以如果没有保证,为什么不将其缓存并获得每次迭代读取堆对象的性能提升呢?
这正是关键字volatile存在的原因。
为了使此辅助类在多线程环境中更好地工作,它应该具有以下内容:
public volatile bool isComplete = false
当然,由于它是自动生成的代码,所以您无法添加它。更好的方法是在读写isCompleted时添加一些lock(),或者使用一些其他现成的同步或线程/任务工具,而不是试图裸机运行它(因为它不会是裸机,因为它是带有GC、JIT和(..)的CLR上的C#)。
调试模式与发布模式之间的差异可能是因为在调试模式下,许多优化被排除在外,因此您可以调试屏幕上看到的代码。因此,while (!isComplete)未经过优化,因此可以在那里设置断点,而且isComplete在方法启动时不会被积极缓存到寄存器或堆栈中,并且在每个循环迭代中从堆上的对象中读取。
顺便说一句,这只是我的猜测。我甚至没有尝试编译它。
顺便说一句,这似乎不是一个bug;它更像是一个非常晦涩的副作用。此外,如果我对此正确的话,那么它可能是语言的缺陷-C#应允许在闭包中捕获并提升为成员字段的局部变量上放置“volatile”关键字。
1)请参见下面Eric Lippert关于volatile和/或这篇非常有趣的文章,其中显示了确保依赖于volatile的代码是安全的..嗯,好的..uh,让我们说OK所涉及的复杂程度。