测试/验证WeakReference

时间:2011-05-06 14:24:05

标签: c# unit-testing weak-references

我想验证设置WeakReference的代码是否意外地保留了对引用对象的强引用。 (这里是an example如何轻易意外地做到这一点。)

这看起来是检查无意中强引用的最佳方法吗?

TestObject testObj = new TestObject();
WeakReference wr = new WeakReference(testObj);

// Verify that the WeakReference actually points to the intended object instance.
Assert.Equals(wr.Target, testObject);

// Force disposal of testObj;
testObj = null;
GC.Collect();
// If no strong references are left to the wr.Target, wr.IsAlive will return false.
Assert.False(wr.IsAlive);

3 个答案:

答案 0 :(得分:12)

我与微软就这一点取得联系,并了解/确认:

  • GC.Collect()强制阻止垃圾回收。
  • GC.Collect()运行时,它不会神秘地跳过符合条件的集合对象。遵循可预测的规则来确定要收集的对象。只要您了解这些规则(即如何处理最终化的对象),您就可以强制销毁某个特定对象,尽管被破坏对象使用的内存可能会被释放,也可能不被释放。

有关我博客的更多信息:Can .Net garbage collection be forced?

答案 1 :(得分:4)

我昨天就这样做了。这是我必须添加的内容,以确保在上一次断言之前发生了收集:

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.WaitForFullGCComplete();
        GC.Collect();

如果在此之后.IsAlive仍然是真的,可能在某处仍然存在强烈的引用。

顺便说一下 - 当您访问WeakReference目标时,请务必不要检查.IsAlive。为了避免在检查.IsAlive和.Target之间出现竞争条件,请执行以下操作:

var r = weakRef.Target AS Something;
if (r != null)
{
    ... do your thing
}

答案 2 :(得分:1)

涉及WeakReference对象的单元测试比您预期的要棘手。正如您和其他人所指出的,GC.Collect()可以“强制”垃圾收集,但这仍然取决于您的对象没有引用。

不幸的是,构建代码的方法可以更改对象是否仍然具有对它们的引用。更具体地说,当对象仍然植根时,无论是在调试模式下还是在释放模式下进行构建,都会发生改变(准确地说,取决于您是否打开了优化功能;调试默认情况下将其关闭,而发布版本则将其默认打开) 。调试模式关闭了许多优化,甚至倾向于将当前正在执行的方法中创建/声明的对象植根。因此,您的单元测试在Debug版本中可能会失败,而在Release版本中会成功。

在您的示例中,即使您将testObj设置为NULL,编译器仍将通过保持其先前值为根来尝试对Debug构建产生帮助。这意味着无论您调用GC.Collect()多少次,wr.IsAlive都将始终返回TRUE。

那么,您如何测试WeakReference?很简单:使用另一种方法创建它们以及它们所基于的对象。只要该方法没有内联,并且在大多数情况下不会,编译器就不会将您关心的对象作为根,并且您可以使测试通过Debug和Release版本。

以下功能为您提供了有关如何执行此操作的提示:

public static Tuple<WeakReference, ManualResetEvent, int> GetKillableWr(Func<object> func, bool useGetHashCode = false)
{
    var foo = func();
    var result = new Tuple<WeakReference, ManualResetEvent, int>(new WeakReference(foo), new ManualResetEvent(false), useGetHashCode ? (foo?.GetHashCode() ?? 0) : RuntimeHelpers.GetHashCode(foo));

    Task.Factory.StartNew(() =>
    {
        result.Item2.WaitOne();
        GC.KeepAlive(foo);  // need this here to make sure it doesn't get GC-ed ahead of time
        foo = null;
    });

    return result;
}

使用此方法,只要在func参数内创建对象 ,就可以为您的对象创建WeakReference选择在您返回返回的ManualResetEvent并调用GC.Collect()之后将不会被植根。正如其他人指出的那样,调用下面的代码以确保在需要时进行清理会很有帮助...

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

编辑:

还有其他一些“陷阱”需要担心。一个常见的涉及String个。 String文字和常量始终植根,因为它们被编译为对DLL / EXE的引用。因此,new WeakReference("foo")之类的东西总是会显示为活动状态,因为“ foo”已存储在您的DLL中,并且在编译后的代码中提供了对该存储文字的引用。解决此问题的一种简单方法是使用new StringBuilder("<your string here>").ToString()代替字符串文字。

再次编辑:

另一个“陷阱”是,在Release版本中,优化导致GC更具攻击性,这与上述情况不同,可能导致对象超出预期范围的超出范围。在下面的代码中,wr.IsAlive有时可以返回FALSE,因为GC已检测到myObject不会被该方法中的其他任何东西使用,因此使其有资格进行垃圾回收。解决方法是将GC.KeepAlive(myObject)放在方法的末尾。这将使myObject扎根,直到至少执行该行。

public static void SomeTest()
{
    var myObject = new object();
    var wr = new WeakReference(myObject);
    GC.Collect();
    Assert.True(wr.IsAlive, "This could fail in Release Mode!");
}