在调试版本中使用垃圾收集奇怪地触发了奇怪的错误

时间:2012-12-24 16:35:20

标签: c# .net debugging garbage-collection

我正在编写垃圾收集测试,偶然发现调试模式中出现了一组奇怪的错误。 这是一个蒸馏的POC代码。 有一个包含20个lambdas的列表以及一个对它们的弱引用列表。 我访问第13个lambda(调用.ToString()),然后清除列表。 然后强制垃圾收集并分析哪些元素在清理过程中幸存下来。

    public void TestGC_WTF() {
        var handlers = new List<Action>();
        var weakReferences = new List<WeakReference>();

        int testValue = 0;
        for (int i = 0; i < 20; i++) {
            int number = i;
            Action handler = () => testValue += number;
            handlers.Add(handler);
            weakReferences.Add(new WeakReference(handler));
            handler = null;
        }

        handlers[13].ToString();

        handlers.Clear();

        GC.Collect();

        if (false) { } //This is required for the bug to occur.

        var aliveReferences = Enumerable.Range(0, weakReferences.Count).Where(i => weakReferences[i].IsAlive).ToArray();
        Console.WriteLine("Uncollected handlers: {0}", string.Join(",", aliveReferences));
    }

在调试模式下,此代码打印(“未收集的处理程序:13,19”。 我对此结果的问题如下:

  1. 未收集第19个元素(即使我在每次循环迭代结束时将handle变量显式设置为null)
  2. 未收集第13个元素(即使我从未将其存储在任何地方)
  3. if (false) { }(或任何其他循环/条件)对于触发错误至关重要。
  4. 导致出现问题的原因是什么? (我知道在调试过程中,对象会被保留,直到它们被定义的块结束。仅此一项仍无法解释我遇到的任何问题。)

    我已将错误提交给Microsoft,但我对问题的原因更感兴趣https://connect.microsoft.com/VisualStudio/feedback/details/775082/strangely-triggered-strange-bugs-with-garbage-collection-in-debug-builds

2 个答案:

答案 0 :(得分:3)

这是x86抖动特有的。是的,这看起来像一个错误,抖动将堆栈帧上的临时存储位置标记为有效的对象引用。与[ebp-74h]一样,存储处理程序[13]引用的插槽和[ebp-68h]保持对处理程序[19]的引用。这些临时代码在x86代码中很常见,因为cpu寄存器数量较少,并且在优化器被禁用时,抖动消耗的费用很少,可以很好地利用少数寄存器。

您可以在connect.microsoft.com上提交反馈报告。然而,它们修复它的可能性非常小,当优化器被禁用时,抖动没有义务使其工作。局部变量的生命周期扩展到方法的末尾,以便于调试。对于声明的局部变量以及抖动分配的临时变量,为True。当然,它最终很重要,您只需将Release版本发布给您的客户。当优化的Release构建时,请随意提起这一点,这对GC的有效性不利。

答案 1 :(得分:2)

我不相信GC.Collect()必然会收集所有可能的垃圾,只会花费精力去做。在调试版本中,优化有利于保持引用活动,因为您更可能希望使用调试器检查它们。我没有看到任何我会称之为bug的行为。