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

时间:2013-09-05 09:20:22

标签: c# garbage-collection weak-references

让我开始演示:

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

此代码按预期工作。垃圾收集结束后,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!
}

我在GC.Collect()行之后向测试添加一个空的try / finally块,并且没有收集弱引用的对象!如果在GC.Collect()之前添加了空的try / finally块,那么测试就会通过。

是什么给出的?任何人都可以解释一下try / finally块在精确度上如何影响对象的生命周期吗?

注意:所有测试都在Debug中完成。在发布中,两个测试都通过了。

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

2 个答案:

答案 0 :(得分:3)

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

虽然看起来,但new object()表达式没有存储在代码中的变量中,但在抖动代码生成器完成之后仍然存在一个。对象引用存储在[ebp-44h]的堆栈帧上,与使用局部变量的方式无法区分。您可以看到的唯一方法是查看生成的机器代码,使用Debug + Windows + Disassembly。这是完全正常的,抖动优化器消除了这些冗余存储器存储,但是在Debug构建中没有启用它。

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

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

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

这一切都很正常,你根本无法对非优化代码中局部变量引用的处理方式做出任何合理的假设。对于任何实际使用在单元测试器中运行的[TestMethod]的人来说,这也应该是一个强烈的警告,从不测试代码的Debug构建,只测试Release构建。它的行为与它在用户机器上的工作方式不同。

答案 1 :(得分:0)

为了使调试更容易,在调试模式下,不会处理在本地声明的对象。虽然我无法使用此代码重现您的问题:

var x = new object();
var h = new WeakReference(x);
GC.Collect();
try { }      // I just add an empty
finally { }  // try/finally block
Console.WriteLine(h.Target != null);
Console.ReadKey();

我能够重现这个问题。如果GC.Collect()能够“收集”new object(),如果您在Console.ReadKey()之后放置断点,则可以看到已经处置的对象(x)。< / p>

有人问过类似的问题:https://stackoverflow.com/a/755688/613130

评论很有意思:

  

由于在发布模式下进行了优化,引用范围仅在其最后一次使用之前有效,而不是在其定义的整个代码块中有效。

显然在调试模式下它是相反的,参考范围是它定义的整个范围。