为什么WeakReference在析构函数中无用?

时间:2010-08-18 07:37:54

标签: c# weak-references

请考虑以下代码:

class Program
{
    static void Main(string[] args)
    {
        A a = new A();
        CreateB(a);

        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("And here's:" + a);
        GC.KeepAlive(a);
    }

    private static void CreateB(A a)
    {
        B b = new B(a);
    }
}

class A
{ }

class B
{
    private WeakReference a;
    public B(A a)
    {
        this.a = new WeakReference(a);
    }

    ~B()
    {
        Console.WriteLine("a.IsAlive: " + a.IsAlive);
        Console.WriteLine("a.Target: " + a.Target);
    }
}

使用以下输出:

a.IsAlive: False
a.Target:
And here's:ConsoleApp.A

为什么它是假的而且是空的? A尚未收集。

编辑:哦,你们没有信心。

我添加了以下几行:

Console.WriteLine("And here's:" + a);
GC.KeepAlive(a);

查看更新的输出。

6 个答案:

答案 0 :(得分:3)

更新了更新问题的答案。

通过新问题,我们将完成以下步骤。

  1. A和B活着并生根,B.a活着并且通过B生根。
  2. 活着,B没有根,有资格收集。 B.a没有扎根和有资格。
  3. 收集发生。 B和B.a都是最终的,所以它们被放在终结者队列中。 B未被收集,因为它是最终的。 B.a未被收集,两者因为它是最终的,并且因为它被B引用但尚未最终确定。
  4. B.a最终确定,或B最终确定。
  5. B.a或B中的另一个已定稿。
  6. B.a和B有资格领取。
  7. (如果B在第4点最终确定,则有可能在第5点之前收集它,因为等待结束的B同时保留B和Ba的收集,等待完成的Ba不会影响B的收集)。

    发生的事情是4到5之间的顺序是B.a最终确定,然后B完成。由于WeakReference对象的引用不是普通引用,因此它需要自己的清理代码来释放它的GCHandle。显然它不能依赖于正常的GC集合行为,因为它的引用的全部意义在于它们不遵循正常的GC集合行为。

    现在B的终结器已经运行,但由于Ba的终结器的行为是释放它的引用,它为IsAlive返回false(或者在1.1之前的.NET中,如果我记得版本正确,则抛出错误)。

答案 1 :(得分:2)

这方面的关键问题是您在终结器中访问引用字段底层问题是WeakReference本身已经(或者可以,不可预测)已经被收集(因为收集顺序是非-deterministic)。简单地说:WeakReference不再存在,您在幽灵对象上查询IsValid / Target等。

因此,访问此对象完全是不可靠和脆弱的。终结者应该与直接值类型状态 - 句柄等交谈。任何引用(除非你知道它将永远超出被销毁的对象)应该被处理不信任和避免。

如果相反,我们传入WeakReference并确保未收集WeakReference,那么一切正常;以下应该显示一个成功(我们已经在WeakReference中传递的那个),一个失败(我们为此对象创建了WeakReference,因此它有资格收集同时 as 对象):

using System;
class Program
{
    static void Main(string[] args)
    {
        A a = new A();
        CreateB(a);

        WeakReference weakRef = new WeakReference(a);
        CreateB(weakRef);
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.KeepAlive(a);
        GC.KeepAlive(weakRef);

        Console.ReadKey();
    }

    private static void CreateB(A a)
    {
        B b = new B(a);
    }
    private static void CreateB(WeakReference a)
    {
        B b = new B(a);
    }
}

class A
{ }

class B
{
    private WeakReference a;
    public B(WeakReference a)
    {
        this.a = a;
    }
    public B(A a)
    {
        this.a = new WeakReference(a);
    }

    ~B()
    {
        Console.WriteLine("a.IsAlive: " + a.IsAlive);
        Console.WriteLine("a.Target: " + a.Target);
    }
}

<击> 是什么让你说它没有被收集?它看起来符合条件....活动对象上没有字段保存它,并且该变量永远不会被读取超过该点(事实上该变量可能已被编译器优化掉了,所以没有“当地“在IL”。

您可能需要GC.KeepAlive(a)底部的Main来阻止它。

答案 2 :(得分:2)

这确实有点奇怪,我不能说,我有答案,但这是我到目前为止所发现的。举个例子,我在调用GC.Collect之前立即附加了WinDbg。此时,弱引用按预期保持实例。

然后我挖出了WeakReference的实际实例,并在引用本身上设置了一个数据断点。从这一点开始,调试器在mscorwks!WKS::FreeWeakHandle+0x12处断开(将句柄设置为null),并且托管调用堆栈如下:

OS Thread Id: 0xf54 (0)
ESP       EIP     
0045ed28 6eb182d3 [HelperMethodFrame: 0045ed28] System.GC.nativeCollectGeneration(Int32, Int32)
0045ed80 00af0c62 System.GC.Collect()
0045ed84 005e819d app.Program.Main(System.String[])
0045efac 6eab1b5c [GCFrame: 0045efac] 

这似乎表明,对GC.Collect的调用反过来也会导致修改弱引用。这可以解释观察到的行为,但我不能说这是否是它在所有情况下的表现。

答案 3 :(得分:1)

  

为什么它是假的而且是空的? A尚未收集。

你肯定不知道。 GC可以在不再需要时立即收集它 - 在这种情况下,它在填充到WeakReference之后就会立即收集。

顺便说一下,R​​aymond Chen最近有blog post这个话题。

答案 4 :(得分:1)

垃圾收集器已确定a已死,因为GC.collect()之后不再引用它。如果您将代码更改为:

GC.Collect();
GC.WaitForPendingFinalizers();
System.Console.WriteLine("And here's:"+a);

在B的最终确定期间,您会发现a存活。

答案 5 :(得分:0)

即使WeakReference未实现IDisposable,它也会使用非托管资源(GCHandle)。当WeakReference被放弃时,它必须确保在WeakReference本身被垃圾收集之前释放资源;如果没有,系统将无法知道不再需要GCHandle。要处理此问题,WeakReference会在其GCHandle方法中释放Finalize(从而使自身无效)。如果在执行试图使用Finalize的{​​{1}}方法之前发生这种情况,则后一种方法将无法获得WeakReference以前的目标。

WeakReference的构造函数接受一个参数,该参数指示一旦其目标有资格立即完成(参数值WeakReference),或者仅当其目标符合条件时,其目标是否应该失效湮灭(参数值false)。我不确定该参数是否会导致true本身在一个GC循环中复活,但这可能是可能的。

否则,如果您使用的是.net 4.0,则有一个名为WeakReference的类可能会有所帮助;它允许链接各种对象的生命周期。你可以有一个finalizable对象,它持有你想要弱引用的对象的强引用,并且对可终结对象的唯一引用存储在ConditionalWeakTable中,由弱引用对象键入是理想的。后一个对象(ConditionalWeakTable Key`的Value做了;然后它可以用它所拥有的强引用做一些合适的事情。