编辑:好的,我在这方面有些进展。可能涉及到三个二进制开关(至少):
- 代码是否经过优化;即命令行中的
/o+
或 /o-
标志。看起来没有任何区别。
- 代码是否在调试器中运行。看起来也没有任何区别。
- 生成的调试信息级别,即命令行标志
/debug+
、/debug-
、/debug:full
或 /debug:pdbonly
。只有 /debug+
或 /debug:full
会导致其失败。
此外:
- 如果将
Main
代码与 TestDestructor
代码分开,则可以确定是 Main
代码的编译模式造成了差异。
- 据我所知,
/debug:pdbonly
生成的 IL 与 /debug:full
在方法本身内部生成的 IL 相同,因此可能是一个清单问题...
编辑:好吧,现在情况真的很奇怪。如果我反汇编 “错误” 版本,然后重新汇编它,它就能正常工作:
ildasm /out:broken.il Program.exe
ilasm broken.il
ilasm有三个不同的调试设置: /DEBUG
, /DEBUG=OPT
和/DEBUG=IMPL
。使用前两个会失败,使用最后一个则可以。最后一个被描述为启用即时编译优化,因此这可能是造成差异的原因......尽管在我看来,它应该仍然能够以任何一种方式收集对象。
这可能是由于DestructorCalled
中的内存模型问题。它不是易失性的,因此无法保证来自终结器线程的写入将被测试线程“看到”。
在这种情况下,确实会调用终结器。在使变量易失性后,这个独立的等效示例(对我来说更简单)当然会打印True。当然,这并不是证明:没有volatile
,代码不能保证失败; 它只是不保证工作。您能否在将其设置为易失性变量后使测试失败?
using System;
class TestDestructor
{
public static volatile bool DestructorCalled;
~TestDestructor()
{
DestructorCalled = true;
}
}
class Test
{
static void Main()
{
TestDestructor testDestructor = new TestDestructor();
var array = new object[] { testDestructor };
array = null;
testDestructor = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(TestDestructor.DestructorCalled);
}
}
编辑:我刚刚发现在使用Visual Studio构建时,这个操作失败了,但是在命令行中运行没有问题。现在我正在研究IL(Intermediate Language)。