在c#中如何知道弱引用对象是否会被垃圾回收?

问题描述 投票:0回答:4

假设我有这样的代码:

class Test
{
    WeakReference m_ref;

    public Test()
    {
        Test1();
        Test2();
    }

    void Test1()
    {
        m_ref = new WeakReference(new object());
    }

    void Test2()
    {
        // If I do the GC then the m_ref.Target is null
        // GC.Collect();
        Debug.Log(m_ref.Target);
    }
}

void TestFunc()
{
    new Test();
}

在这个例子中,我创建了一个新的对象实例,并将其设置为WeakReference中的Test1实例。如果我在退出Test1之后理解正确,那么对象实例就没有任何引用,所以这个实例很快就会成为GC

但是,在Test2中如果没有执行GC,我仍然可以通过m_ref.Target访问对象实例。

有没有办法我可以知道m_ref.Target无效而无需手动执行GC

c# weak-references
4个回答
3
投票

不,你不能。根据设计,WeakReference与垃圾收集器紧密耦合。甚至documentation也提到它:

获取当前WeakReference对象引用的对象是否已被垃圾回收的指示。

据我所知,C#中没有办法知道是否仍然存在对给定对象的活动引用,除非可以手动浏览整个引用树(并且几乎自己重新实现了GC)。


2
投票

有没有办法在没有手动执行GC的情况下知道m_ref.Target是无效的?

在GC收集对象之前,它无效。垃圾收集的重点是您不知道,并且您不必在丢弃对象时进行处理。

在你的例子中,是的,你是正确的,在执行m_ref = new WeakReference(new object());之后,将很快收集实例。然而,'很快'没有具体定义,所以你不能假设这将在Test2被调用和Debug.Log(m_ref.Target);执行之前发生。


0
投票

如果在退出Test1后我理解正确,那么对象实例就没有任何引用...

你错了。从技术上讲,你没有任何创建对象的引用,它可能是下一行的gc'ed。

考虑这个简单的例子:

class Program
{
    static WeakReference _ref;

    static void Main(string[] args)
    {
        Test();
        GC.Collect();
        Console.WriteLine(_ref.IsAlive); // false
    }

    static void Test()
    {
        var obj = new object();
        _ref = new WeakReference(obj);
        GC.Collect();
        Console.WriteLine(_ref.IsAlive); // true
    }
}

Test()中,我们强烈地提到了一个对象,它确实持续到方法结束。

你可以做这样的事情来确定

object obj = _ref.Target;
if (obj != null)
{
    ... safe to do something with obj
}

0
投票

您无法判断WeakReference是否有效。但你可以判断它是否无效。我知道这很奇怪。如果我有代码执行if语句并且可以询问“它是否有效”,那么它可能在下一行代码中无效,因此它是无用的。对WeakReference的TryGetTarget调用获取对该对象的引用,或者失败并返回false。一旦它有一个引用,引用就可以防止对象被垃圾回收,因此它至少与你有引用一样长久保持有效。

在某些代码中,可能会保留List <WeakReference <MyNiftyClass >>

跟踪该列表并保持其未被引用(其他地方)引用的一个好方法是让一些for循环使用TryGetTarget扫描列表,如果失败则从列表中删除过时引用。但是这样的删除会破坏迭代器,因此您希望将RemoveAll与谓词一起使用。我有一个名为CognateBase的类和一个名为AllCognateBases的静态全局列表。 CognateBase有一个Tick()函数,我每次调用程序时都会调用它。 Tick循环是一个收获过时参考的好地方。所以我有...

    public static void TickAll()
    {
        // This will loop through all CognateBase objects and call their Tick, or if deleted from memory, remove the CognateBase.
        AllCognateBases.RemoveAll(_TickIfAble);
    }

然后_TickIfAble是

private static bool _TickIfAble(WeakReference<CognateBase> r)
    {
        CognateBase cb;
        if (r.TryGetTarget(out cb))
        {
            cb.Tick();
            return false;
        }
        else
        {
            return true;
        }
    }

因此,有效的CognateBase实例将被勾选,并且不再有效的那些实例将被删除。因为它在RemoveAll中没有任何迭代器搞砸了。

代码中我引用了CognateBase并将其设置为null的其他任何地方,最终将从列表中删除并删除CognateBase。

这个测试工作。所有这些GC调用都是为了强制垃圾收集现在发生,而不是在C#感觉它的时候。

        public static void UnitTest()
    {
        Debug.Assert(AllCognateBases.Count == 0);
        CognateBase b1 = new CognateBase();
        Debug.Assert(AllCognateBases.Count == 1);
        CognateBase b2 = new CognateBase();
        Debug.Assert(AllCognateBases.Count == 2);
        GC.Collect();
        Debug.Assert(AllCognateBases.Count == 2);

        b1 = null;
        GC.Collect();
        GC.WaitForFullGCComplete();
        GC.WaitForPendingFinalizers();
        TickAll();
        GC.Collect();
        GC.WaitForFullGCComplete();
        GC.WaitForPendingFinalizers();
        Debug.Assert(AllCognateBases.Count == 1);

        b2 = null;
        GC.Collect();
        GC.WaitForFullGCComplete();
        GC.WaitForPendingFinalizers();
        TickAll();
        GC.Collect();
        GC.WaitForFullGCComplete();
        GC.WaitForPendingFinalizers();
        Debug.Assert(AllCognateBases.Count == 0);
    }

而创作者......

       public CognateBase()
    {
        AllCognateBases.Add(new WeakReference<CognateBase>(this));
    }

警告 - 当引用设置为null时,如上所述b1 = null;对象可能不会在looooooong时间收集垃圾。所有这一切它仍然有效,并将得到它的Tick叫!

© www.soinside.com 2019 - 2024. All rights reserved.