代码之家  ›  专栏  ›  技术社区  ›  Bubblewrap

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

  •  10
  • Bubblewrap  · 技术社区  · 14 年前

    考虑以下代码:

    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 回复  |  直到 12 年前
        1
  •  3
  •   Jon Hanna    14 年前

    更新问题的更新答案。

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

    1. A是活的,B是没有根的,没有资格收藏。B.a.不合格。
    2. 收集发生了。B和B.a都是可最终确定的,因此它们被放在最终确定者队列中。B未被收集,因为它是最终确定的。未收取学士学位, 二者都
    3. B.a最终确定,或B最终确定。
    4. B.a和B可供收集。

    (如果B在第4点最终确定,则有可能在第5点之前收回,因为等待最终确定的B使B和B.a都无法收回,等待最终确定的B.a不影响B的收回)。

    所发生的是,在4和5之间的顺序是这样的,B.a被敲定,然后B被敲定。由于WeakReference对对象的引用不是普通的引用,因此它需要自己的清理代码来释放GCHandle。显然,它不能依赖于正常的GC收集行为,因为它引用的全部要点是它们不遵循正常的GC收集行为。

    现在B的finalizer已经运行了,但是由于B.a的finalizer的行为是释放它的引用,因此对于IsAlive它返回false(或者在1.1之前的.NET中,如果我记住了正确的版本,则抛出一个错误)。

        2
  •  2
  •   Marc Gravell    14 年前

    参考场 在终结器中。这个 潜在的 问题是 WeakReference 本身有(或 是,不可预测的) 弱参照 已不存在,您正在查询 IsValid / Target

    所以访问这个对象 完全 是不可靠和脆弱的。终结器应该 只有 与直接值类型state-handles等交谈。任何引用(除非 它将永远活在(被摧毁的物体)应该以不信任的态度对待和避免。

    弱参照 并确保 弱参照 ),一次失败(我们创建了 弱参照 作为 对象):

    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 阻止它。

        3
  •  2
  •   Brian Rasmussen    14 年前

    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

        4
  •  1
  •   Anon.    14 年前

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

    你不确定。GC可以在不再需要它时立即收集它—在本例中,就是在它被塞进WeakReference之后。

    顺便说一句,雷蒙德·陈 blog post 最近关于这个话题。

        5
  •  1
  •   Nordic Mainframe    14 年前

    垃圾收集器已确定 a 已经死了,因为之后就再也没有了GC.收集(). 如果将代码更改为:

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

    你会发现 在B。

        6
  •  0
  •   supercat    12 年前

    尽管 WeakReference IDisposable ,它确实使用非托管资源(a) GCHandle 如果已放弃,则必须确保在 它自己会被垃圾收集起来;如果没有,系统将无法知道 GCHandle命令 不再需要了。为了解决这个问题 弱参照 释放其 GCHandle命令 (从而使自己无效)在其 Finalize 方法。如果这种情况发生在 尝试使用 弱参照 ,后一种方法将无法获得 以前的目标。

    的构造函数 弱参照 接受一个参数,该参数指示在其目标符合立即终结条件时是否应立即使其目标无效(参数值 false true ). 我不确定这个参数是否会导致 在一个GC循环中,它自己可能会复活,但这可能是一种可能性。

    否则,如果您使用的是.NET4.0,则有一个名为 ConditionalWeakTable 条件脆弱表 Value ConditionalWeakTable entry) will become eligible for finalization when its corresponding Key`does;然后它可以用它所持有的强引用做一些合适的事情。