C# 垃圾收集应已删除对象,但WeakReference.IsAlive仍返回true

C# 垃圾收集应已删除对象,但WeakReference.IsAlive仍返回true,c#,.net,garbage-collection,weak-references,C#,.net,Garbage Collection,Weak References,我有一个我期望通过的测试,但垃圾收集器的行为并不像我想象的那样: [Test] public void WeakReferenceTest2() { var obj = new object(); var wRef = new WeakReference(obj); wRef.IsAlive.Should().BeTrue(); //passes GC.Collect(); wRef.IsAlive.Should().BeTrue(); //pass

我有一个我期望通过的测试,但垃圾收集器的行为并不像我想象的那样:

[Test]
public void WeakReferenceTest2()
{
    var obj = new object();
    var wRef = new WeakReference(obj);

    wRef.IsAlive.Should().BeTrue(); //passes

    GC.Collect();

    wRef.IsAlive.Should().BeTrue(); //passes

    obj = null;

    GC.Collect();

    wRef.IsAlive.Should().BeFalse(); //fails
}
在本例中,
obj
对象应该是GC'd,因此我希望
WeakReference.IsAlive
属性返回
false

似乎是因为
obj
变量声明在与
GC.Collect
相同的范围内,所以它没有被收集。如果我将obj声明和初始化移到方法之外,测试就会通过


有人对此行为有任何技术参考文档或解释吗?

可能是
.Should()
扩展方法在某种程度上依赖于一个参考吗?或者可能是测试框架的其他方面导致了这个问题

(我将此作为答案发布,否则我无法轻松发布代码!)

我已经尝试了以下代码,并按预期工作(Visual Studio 2012、.Net 4构建、调试和发布,32位和64位,在Windows 7上运行,四核处理器):


尝试此代码时会发生什么情况?

据我所知,调用
Collect
不能保证释放所有资源。你只是在向垃圾收集器提建议

通过执行以下操作,可以尝试强制其阻止,直到释放所有对象:

GC.Collect(2, GCCollectionMode.Forced, true);

我预计这可能不会在100%的情况下完全奏效。一般来说,我会避免编写任何依赖于观察垃圾收集器的代码,它并不是真正设计成这样使用的。

我可以看到一些潜在的问题:

  • 我不知道C#规范中有任何要求限制局部变量生存期的内容。在非调试构建中,我认为编译器可以自由省略对
    obj
    的最后一个赋值(将其设置为
    null
    ),因为没有代码路径会导致
    obj
    的值在它之后永远不会被使用,但我希望在非调试构建中,元数据将指示在创建弱引用之后永远不会使用该变量。在调试构建中,变量应该存在于整个函数范围内,但是
    obj=null语句实际上应该清除它。尽管如此,我不确定C#规范是否承诺编译器不会省略最后一条语句,但仍然保留变量

  • 如果您使用的是并发垃圾收集器,则可能是
    GC.Collect()
    触发立即开始收集,但在
    GC.Collect()
    返回之前,收集实际上不会完成。在这种情况下,可能不需要等待所有终结器运行,因此,
    GC.WaitForPendingFinalizers()
    可能有些过分,但它可能会解决问题

  • 在使用标准垃圾收集器时,我不希望存在对对象的弱引用会像终结器那样延长对象的存在,但在使用并发垃圾收集器时,可能存在弱引用的废弃对象被移动到需要清理的具有弱引用的对象队列中,并且此类清理的处理发生在与其他所有对象同时运行的单独线程上。在这种情况下,需要调用
    GC.WaitForPendingFinalizers()
    ,才能实现所需的行为


注意,人们通常不应该期望弱引用会以任何特定的及时性程度失效,也不应该期望在
IsAlive
reports true之后获取
Target
将产生非空引用。只有在不关心目标是否还活着的情况下,才应该使用
IsAlive
,但有兴趣知道引用已死亡。例如,如果有一个
WeakReference
对象的集合,则可能希望定期遍历列表并删除目标已死亡的
WeakReference
对象。人们应该准备好,
WeakReferences
可能在集合中停留的时间比理想情况下需要的时间更长;如果他们这样做,唯一的后果应该是稍微浪费内存和CPU时间。

遇到了与您相同的问题-我的测试到处都通过了,除了在NCrunch下(在您的情况下可能是任何其他仪器)。Hm.通过SOS进行调试,发现测试方法的调用堆栈上有额外的根。我的猜测是,它们是禁用任何编译器优化(包括正确计算对象可达性的优化)的代码插装的结果

这里的解决方法很简单——永远不要持有来自进行GC和有效性测试的方法的强引用。这可以通过一个简单的助手方法轻松实现。下面的更改使您的测试用例通过了NCrunch,而它最初是失败的

[TestMethod]
public void WeakReferenceTest2()
{
    var wRef2 = CallInItsOwnScope(() =>
    {
        var obj = new object();
        var wRef = new WeakReference(obj);

        wRef.IsAlive.Should().BeTrue(); //passes

        GC.Collect();

        wRef.IsAlive.Should().BeTrue(); //passes
        return wRef;
    });

    GC.Collect();

    wRef2.IsAlive.Should().BeFalse(); //used to fail, now passes
}

private T CallInItsOwnScope<T>(Func<T> getter)
{
    return getter();
}
[TestMethod]
public void WeakReferenceTest2()
{
var wRef2=CallInItsOwnScope(()=>
{
var obj=新对象();
var wRef=新的WeakReference(obj);
wRef.IsAlive.Should().BeTrue();//通过
GC.Collect();
wRef.IsAlive.Should().BeTrue();//通过
返回鹪鹩;
});
GC.Collect();
wRef2.IsAlive.Should().BeFalse();//过去失败,现在通过
}
专用T CallInItsOwnScope(Func getter)
{
返回getter();
}

我觉得您需要调用,因为我希望finalizers线程会更新周引用

多年前,我在编写单元测试时遇到了问题,回想起来,
WaitForPendingFinalizers()
有帮助,调用
GC.Collect()
也有帮助

该软件在现实生活中从未泄漏,但编写了一个单元测试来证明objec
[TestMethod]
public void WeakReferenceTest2()
{
    var wRef2 = CallInItsOwnScope(() =>
    {
        var obj = new object();
        var wRef = new WeakReference(obj);

        wRef.IsAlive.Should().BeTrue(); //passes

        GC.Collect();

        wRef.IsAlive.Should().BeTrue(); //passes
        return wRef;
    });

    GC.Collect();

    wRef2.IsAlive.Should().BeFalse(); //used to fail, now passes
}

private T CallInItsOwnScope<T>(Func<T> getter)
{
    return getter();
}