为什么弱引用在析构函数中没有用?

发布于 2024-09-15 00:17:47 字数 894 浏览 3 评论 0原文

考虑以下代码:

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

为什么它是 false 和 null? A 还没收集到。

编辑:哦,你们这些小信仰。

我添加了以下行:

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

请参阅更新的输出。

Consider the following code:

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

With the following output:

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

Why is it false and null? A hasn't been collected yet.

EDIT: Oh ye of little faith.

I added the following lines:

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

See the updated output.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

我的痛♀有谁懂 2024-09-22 00:17:47

更新问题的更新答案。

有了新问题,我们将执行以下步骤。

  1. A 和 B 活着并已生根,Ba 活着并通过 B 生根。A
  2. 活着,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 中,如果我没记错版本,则会引发错误)。

Updated answer for updated question.

With the new question, we go through the following steps.

  1. A and B alive and rooted, B.a alive and rooted via B.
  2. A alive, B not rooted and eligible for collection. B.a not rooted and eligible.
  3. Collection happens. B and B.a are each finalisable, so they are put on the finaliser queue. B is not collected because it is finalisable. B.a is not collected, both because it is finalisable, and because it is referenced by B which has not yet been finalised.
  4. Either B.a is finalised, or B is finalised.
  5. The other of B.a or B is finalised.
  6. B.a and B are eligibled for for collection.

(If B was finalised at point 4 it would be possible for it to be collected before point 5, as while B awaiting finalisation keeps both B and B.a from collection, B.a awaiting finalisation does not affect B's collection).

What has happened is that the order between 4 and 5 was such that B.a was finalised and then B was finalised. Since the reference a WeakReference holds to an object is not a normal reference, it needs its own clean-up code to release its GCHandle. Obviously it can't depend on normal GC collection behaviour, since the whole point of its references is that they don't follow normal GC collection behaviour.

Now B's finaliser is run, but since the behaviour of B.a's finaliser was to release its reference it returns false for IsAlive (or in .NET prior to 1.1 if I'm remembering the versions right, throws an error).

命硬 2024-09-22 00:17:47

其中的关键问题是您在终结器期间访问引用字段潜在问题是WeakReference本身已经(或者可能,不可预测)已经被收集(自收集以来)顺序是不确定的)。简单地说:WeakReference 不再存在,并且您在幽灵对象上查询 IsValid / Target 等。

因此,完全访问该对象是不可靠且脆弱的。终结器应该与直接值类型状态 - 句柄等对话。任何引用(除非您知道它总是比被销毁的对象更长寿)应该被处理不信任并回避。

相反,如果我们传入 WeakReference 并确保 WeakReference 不被收集,那么一切都会正常;下面应该显示一种成功(我们传入 WeakReference 的情况),一种失败(我们仅为该对象创建 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 中的“local”)。

您可能需要在 Main 底部使用 GC.KeepAlive(a) 来停止它。

The key problem in this is that you are accessing a reference field during a finalizer. The underlying problem is that the WeakReference itself has (or can be, unpredictably) already been collected (since collection order is non-deterministic). Simply: the WeakReference no longer exists, and you are query IsValid / Target etc on a ghost object.

So accessing this object at all is unreliable and brittle. Finalizers should only talk to direct value-type state - handles, etc. Any reference (unless you know it will always out-live the object being destroyed) should be treated with distrust and avoided.

If instead, we pass in the WeakReference and ensure that the WeakReference is not collected, then everything works fine; the following should show one success (the one where we've passed in the WeakReference), and one fail (where we've created the WeakReference just for this object, thus it is eligible for collection at the same time as the object):

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


What makes you say it isn't collected? It looks eligible.... no field on a live object holds it, and the variable is never read past that point (and indeed that variable may well have been optimised away by the compiler, so no "local" in the IL).

You might need a GC.KeepAlive(a) at the bottom of Main to stop it.

爱的十字路口 2024-09-22 00:17:47

这确实有点奇怪,我不能说我有答案,但这是我迄今为止发现的。根据您的示例,我在调用 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] 

这似乎表明,对 < code>GC.Collect 反过来最终也会修改弱引用。这可以解释观察到的行为,但我不能说这是否是所有情况下的行为方式。

This is indeed a bit odd and I can't say, that I have the answer, but here's what I have found so far. Given your example, I attached WinDbg immediately before the call to GC.Collect. At this point the weak reference holds on to the instance as expected.

Following that I dug out the actual instance of WeakReference and set a data breakpoint on the reference itself. Proceeding from this point the debugger breaks at mscorwks!WKS::FreeWeakHandle+0x12 (which sets the handle to null) and the managed call stack is as follows:

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] 

This seems to indicate, that the call to GC.Collect in turn ends up modifying the weak reference as well. This would explain the observed behavior, but I can't say if this is how it will behave in all cases.

意中人 2024-09-22 00:17:47

为什么它是 false 和 null? A尚未收集。

你不确定。一旦不再需要它,GC 就会立即收集它 - 在本例中,就是在它被填充到 WeakReference 中之后。

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

Why is it false and null? A hasn't been collected yet.

You don't know that for sure. The GC can collect it as soon as it's no longer needed - which, in this case, is right after it gets stuffed into the WeakReference.

Incidentally, Raymond Chen had a blog post recently about this very topic.

樱&纷飞 2024-09-22 00:17:47

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

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

你会发现a在B的终结过程中还活着。

The garbage collector has determined that ais dead because it is not referred anymore after GC.collect(). If you change the code to:

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

You will find a alive during the finalization of B.

辞旧 2024-09-22 00:17:47

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

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

否则,如果您使用 .net 4.0,则有一个名为 ConditionalWeakTable 的类可能会有所帮助;它允许链接各种对象的生命周期。您可以拥有一个可终结对象,该对象持有对您想要弱引用的对象的强引用,并且对该可终结对象的唯一引用存储在 ConditionalWeakTable 中,并由要弱引用的对象作为键控。需要弱引用。后一个对象(ConditionalWeakTable 条目的 Value)将在其对应的Key` 完成时获得最终确定的资格;然后它可以利用它所拥有的强大参考来做一些合适的事情。

Even though WeakReference does not implement IDisposable, it does use an unmanaged resource (a GCHandle). When a WeakReference is abandoned, it must make certain that resource gets released before the WeakReference itself gets garbage-collected; if it did not, the system would have no way of knowing that the GCHandle was no longer needed. To deal with this, a WeakReference release its GCHandle (thus invalidating itself) in its Finalize method. If this happens before the execution of a Finalize method that attempts to use the WeakReference, the latter method will have no way of getting the WeakReference's former target.

The constructor for WeakReference accepts a parameter which indicates whether its target should be invalidated as soon as its target becomes eligible for immediate finalization (parameter value false), or only when its target becomes eligible for annihilation (parameter value true). I'm not sure whether that parameter will cause the WeakReference itself to be resurrected for one GC cycle, but that might be a possibility.

Otherwise, if you are using .net 4.0, there is a class called ConditionalWeakTable that might be helpful; it allows the lifetimes of various objects to be linked. You could have a finalizable object which holds a strong reference to the object to which you want a weak reference, and have the only reference to that finalizable object be stored in a ConditionalWeakTable, keyed by the object to which the weak reference is desired. The latter object (the Value of the ConditionalWeakTable entry) will become eligible for finalization when its correspondingKey` does; it could then do something suitable with the strong reference it holds.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文