为什么try/finally块的存在会阻止垃圾收集器工作?

Ste*_*nev 7 c# garbage-collection weak-references

让我先从演示开始:

[TestMethod]
public void Test()
{
    var h = new WeakReference(new object());
    GC.Collect();
    Assert.IsNull(h.Target);
}
Run Code Online (Sandbox Code Playgroud)

此代码按预期工作.垃圾收集结束后,引用将h被取消.现在,这是扭曲:

[TestMethod]
public void Test()
{
    var h = new WeakReference(new object());
    GC.Collect();
    try { }      // I just add an empty
    finally { }  // try/finally block
    Assert.IsNull(h.Target); // FAIL!
}
Run Code Online (Sandbox Code Playgroud)

我添加一个空try/finally块来测试GC.Collect()线和LO,弱引用的对象,不收集!如果空的try/finally块加入之前GC.Collect()线,测试虽然通过.

是什么赋予了?谁能解释一下try/finally块如何影响对象的生命周期?

注意:所有测试都在Debug中完成.在Release中,两个测试都通过.

注意2:要重现应用程序,必须以.NET 4或.NET 4.5运行时为目标,并且必须以32位运行(目标x86或任何选中"Prefer 32-bit"选项的CPU)

Han*_*ant 3

当连接调试器时,抖动会改变局部变量的生命周期。这个答案里有详细解释。简而言之,如果没有调试器,生命周期在代码中最后一次使用变量时结束,而使用调试器,生命周期将扩展到方法的末尾,以允许 Watch 调试器表达式工作。

虽然表达式看起来new object()没有存储在代码中的变量中,但在抖动代码生成器完成处理后仍然存在一个表达式。对象引用存储在堆栈帧的 [ebp-44h] 上,与局部变量的使用方式没有区别。您可以看到这一点的唯一方法是查看生成的机器代码,使用“调试”+“Windows”+“反汇编”。否则这是完全正常的,这些冗余内存存储已被抖动优化器消除,但在调试版本中未启用。

即使它是临时的,这个变量仍然需要报告给GC作为存储引用。当 GC 在对象构造函数调用和 WeakReference 构造函数调用之间发生时,必须防止对象被收集。如果程序中的另一个线程触发收集,则可能发生。

如果没有 try/finally 块,抖动仍然可以发现堆栈帧槽存储临时值,并且实际上不需要延长其生命周期。因此,在 GC.Collect() 调用和对象被收集之前,它会停止报告临时对象的生命周期。

但对于 try/finally 块,抖动会放弃尝试找出 try 或 finally 块中是否可能使用堆栈帧槽。并通过简单地将其生命周期延长到方法的末尾来解决问题,就像普通的局部变量一样。

这很正常,您根本无法对非优化代码中处理局部变量引用的方式做出任何合理的假设。这也应该是对任何实际使用在单元测试器中运行的 [TestMethod] 的人的强烈警告,永远不要测试代码的调试版本,而只测试发布版本。它的行为方式与在用户计算机上的工作方式不同。