C# 短弱引用何时变为空?

C# 短弱引用何时变为空?,c#,memory-management,garbage-collection,weak-references,C#,Memory Management,Garbage Collection,Weak References,我在类Foo中使用WeakReference()跟踪对象。这个类有一个析构函数,我需要在其中访问被跟踪的对象。我跟踪的对象也在使用WeakReference跟踪Foo 所以现在我想知道,WeakReference的“归零”到底是什么时候发生的?是否所有WeakReference在任何终结器运行之前都被置空,还是在它们跟踪的对象的终结器即将运行之前都被置空 更新 现在我还想知道Monoproject是否能对这一点(,)有所帮助。但是我有点担心,可能MS-GC和Mono-GC处理这个问题的方式不同,

我在类
Foo
中使用
WeakReference
()跟踪对象。这个类有一个析构函数,我需要在其中访问被跟踪的对象。我跟踪的对象也在使用
WeakReference
跟踪
Foo

所以现在我想知道,
WeakReference
的“归零”到底是什么时候发生的?是否所有
WeakReference
在任何终结器运行之前都被置空,还是在它们跟踪的对象的终结器即将运行之前都被置空

更新


现在我还想知道
Mono
project是否能对这一点(,)有所帮助。但是我有点担心,可能
MS-GC
Mono-GC
处理这个问题的方式不同,并且不兼容。

您可以用一个简单的测试程序自己验证。但是我发现
WeakReference
类型本身的文档比您看到的页面更清晰

特别是,在链接页面中称为“短”和“长”的标志在中称为
trackresurvation
。参数的说明如下所示:

指示何时停止跟踪对象。如果为true,则在完成后跟踪对象;如果为false,则仅跟踪对象直到完成

“备注”一节还写道:

如果TrackResurvation为false,则会创建一个简短的弱引用。如果TrackResurvation为true,则会创建一个长弱引用

这确认了当您使用“短”弱引用时,
WeakReference
对象将不再跟踪最终确定的对象(即,
目标
变为
null
),但当您使用“长”弱引用时,它将被跟踪

对于这两种弱引用,实际上已被垃圾收集的对象肯定不再被跟踪(显然)


通常,当终结器线程实际执行其工作时,程序中的任何其他线程都不能观察对象,因此当
Target
属性设置为
null
时,“短”弱引用的精确时刻对我来说似乎没有意义。如果程序中的其他线程观察到该值为非null,则终结器尚未运行。如果它将其观察为null,则终结器已运行。“其他线程”本身不应该在终结器线程工作时运行,因此就“其他线程”而言,终结本质上应该是原子的。

我想写一个演示程序来演示这一区别。结果比我想象的更具挑战性。第一个必要的因素是确保终结器线程的速度可以减慢,这样您就可以观察WeakReference.IsAlive的值,而不必冒与终结器线程争用的风险。所以我用了:

class FinalizerDelayer {
    ~FinalizerDelayer() {
        Console.WriteLine("Delaying finalizer...");
        System.Threading.Thread.Sleep(500);
        Console.WriteLine("Delay done");
    }
}
然后一个小类将成为WeakReference的目标:

class Example {
    private int instance;
    public Example(int instance) { this.instance = instance; }
    ~Example() {
        Console.WriteLine("Example {0} finalized", instance);
    }
}
然后是一个演示长弱引用和短弱引用之间差异的程序:

class Program {
    static void Main(string[] args) {
        var target1 = new Example(1);
        var target2 = new Example(2);
        var shortweak = new WeakReference(target1);
        var longweak = new WeakReference(target2, true);
        var delay = new FinalizerDelayer();
        GC.Collect();       // Kills short reference
        Console.WriteLine("Short alive = {0}", shortweak.IsAlive);
        Console.WriteLine("Long  alive = {0}", longweak.IsAlive);
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Finalization done");
        GC.Collect();       // Kills long reference
        Console.WriteLine("Long  alive = {0}", longweak.IsAlive);
        Console.ReadLine();
    }
}
必须运行此程序,以便调试器不会影响对象的生存期。选择发布版本并更改调试器设置:工具+选项、调试、常规,取消选中“抑制JIT优化”选项

事实证明,对象的终结顺序确实是不确定的。每次运行程序的顺序都不同。我们希望首先完成FinalizerDelayer对象,但这并不总是发生。我认为这是内置地址空间布局随机化功能的一个副作用,它使托管代码很难受到攻击。但经常运行它,您最终会得到:

延迟终结器…
短活动=假
长寿=真
延迟完成
示例1已定稿
示例2已定稿
完成定稿
长寿=假

长话短说:

  • 一旦对象被收集并放置在freachable队列上,一个简短的弱引用就会将IsAlive设置为false,以便最终确定它。该对象在物理上仍然存在,但不再存在强引用,它将很快完成
  • 长弱引用在对象的真实生存期(包括其在freachable队列上的生存期)中全程跟踪对象。IsAlive在完成其终结器之前不会设置为false

当对象被复活时要小心一个怪癖,当强引用被重新创建时,要从freachable队列移回正常堆。这不是我在这个演示程序中探讨的东西,但是需要一个很长的弱参考来观察它。你需要一个很长的弱引用的基本原因。

经过大量研究,我找到了这篇古老的文章(讨论复活和freachable队列):

下面是垃圾收集(GC)运行时发生的情况:

  • 垃圾收集器构建所有可访问对象的图表。本文的第1部分讨论了这一工作原理

  • 垃圾收集器扫描短弱引用表。如果表中的指针引用的对象不是图形的一部分,则指针标识无法访问的对象,并且短弱引用表中的插槽设置为空

  • 垃圾收集器扫描终结队列。如果队列中的指针引用的对象不是图形的一部分,则该指针标识无法访问的对象,并将指针从终结队列移动到可刷新队列。此时,对象被添加到图形中,因为该对象现在被认为是可访问的

  • 垃圾收集器扫描长弱引用表。如果表中的指针引用的对象不是图形的一部分(现在包含entri指向的对象)