10

考虑以下代码:

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);

查看更新的输出。

4

6 回答 6

3

更新问题的更新答案。

对于新问题,我们将执行以下步骤。

  1. A 和 B 活着并生根,Ba 活着并通过 B 生根。
  2. A 活着,B 没有生根,有资格收集。巴没有根和资格。
  3. 收集发生。B 和 Ba 都是可终结的,因此它们被放入终结者队列。B 没有被收集,因为它是可终结的。Ba 未被收集,既是因为它是可终结的,也是因为它被尚未最终确定的 B 引用。
  4. 要么 Ba 完成,要么 B 完成。
  5. Ba或B中的另一个是最终确定的。
  6. Ba 和 B 有资格领取。

(如果 B 在第 4 点完成,则它有可能在第 5 点之前被收集,因为虽然 B 等待完成会阻止 B 和 Ba 被收集,但等待完成的 Ba 不会影响 B 的收集)。

发生的事情是 4 和 5 之间的顺序是这样的,即 Ba 已完成,然后 B 已完成。由于 WeakReference 对对象持有的引用不是普通引用,因此它需要自己的清理代码来释放其 GCHandle。显然它不能依赖于正常的 GC 收集行为,因为它的引用的全部意义在于它们不遵循正常的 GC 收集行为。

现在 B 的终结器已运行,但由于 Ba 的终结器的行为是释放其引用,因此它为 IsAlive 返回 false(或者在 1.1 之前的 .NET 中,如果我没记错版本,则会引发错误)。

于 2010-08-18T07:53:29.810 回答
2

其中的关键问题是您在终结器期间访问参考字段。潜在的问题是它WeakReference本身已经(或可能,不可预测地)已经被收集(因为收集顺序是不确定的)。简单地说:WeakReference不再存在,而你正在查询IsValid/Target等一个幽灵对象。

所以访问这个对象不可靠和脆弱的。终结器应该与直接值类型的状态——句柄等对话。任何引用(除非你知道它总是会比被销毁的对象更长寿)应该被不信任地对待并避免。

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

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)在底部使用aMain来阻止它。

于 2010-08-18T07:45:25.503 回答
2

这确实有点奇怪,我不能说我有答案,但这是我迄今为止发现的。鉴于您的示例,我在调用GC.Collect. 此时,弱引用按预期保留了实例。

之后,我挖出了实际的实例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最终也会修改弱引用。这可以解释观察到的行为,但我不能说这是否是它在所有情况下的行为方式。

于 2010-08-18T08:31:42.707 回答
1

为什么它是假的和空的?A 还没有被收集到。

你不确定。GC 可以在不再需要它时立即收集它——在这种情况下,就是在它被填充到 WeakReference 之后。

顺便说一句,Raymond Chen最近有一篇关于这个话题的博客文章。

于 2010-08-18T07:46:43.590 回答
1

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

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

a在 B 的定稿期间你会发现活着。

于 2010-08-18T07:47:56.043 回答
0

即使WeakReference没有实现IDisposable,它确实使用了非托管资源(a GCHandle)。当 aWeakReference被废弃时,它必须确保在WeakReference它本身被垃圾收集之前释放资源;如果没有,系统将无法知道GCHandle不再需要 。为了解决这个问题,在其方法中WeakReference释放它GCHandle(从而使自身无效) 。如果在尝试使用 的方法Finalize执行之前发生这种情况,则后一种方法将无法获取的前一个目标。FinalizeWeakReferenceWeakReference

for 的构造函数WeakReference接受一个参数,该参数指示其目标是否应在其目标符合立即终止条件时立即失效(参数 value false),或者仅在其目标符合条件时失效(参数 value true)。我不确定该参数是否会导致WeakReference自身在一个 GC 周期内复活,但这可能是一种可能性。

否则,如果您使用的是 .net 4.0,则有一个名为的类ConditionalWeakTable可能会有所帮助;它允许链接各种对象的生命周期。您可以拥有一个可终结的对象,该对象持有对您想要弱引用的对象的强引用,并将对该可终结对象的唯一引用存储在 a 中ConditionalWeakTable,由需要弱引用的对象键入。后一个对象(Key` 的对象)ValueConditionalWeakTable entry) will become eligible for finalization when its corresponding然后它可以做一些适合它持有的强引用的事情。

于 2012-06-19T15:22:04.533 回答