Допустим, у меня есть такой код:

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?

2
jayatubi 6 Сен 2016 в 17:47

4 ответа

Лучший ответ

Нет, не можешь. По своей конструкции WeakReference тесно связан со сборщиком мусора. Об этом упоминается даже в документации:

Получает информацию о том, был ли объект, на который ссылается текущий объект WeakReference , был произведен сборщиком мусора.

Насколько мне известно, в C # нет способа узнать, есть ли еще живые ссылки на данный объект, за исключением, возможно, ручного просмотра всего дерева ссылок (и в значительной степени повторной реализации GC самостоятельно).

3
Kevin Gosse 6 Сен 2016 в 14:53

Могу ли я узнать, что m_ref.Target недействителен, без ручного выполнения GC?

Это не недействительно, пока сборщик мусора не соберет объект. Суть сборки мусора в том, что вы не знаете и не должны заботиться о том, когда объект будет удален.

В вашем примере, да, вы правы, что после выполнения m_ref = new WeakReference(new object()); экземпляр будет собран «скоро». Однако «скоро» вообще не определено, поэтому нельзя предполагать, что это произойдет до вызова Test2 и выполнения Debug.Log(m_ref.Target);.

2
Ondrej Tucny 6 Сен 2016 в 14:52

Если я правильно понимаю, после выхода из Test1, на экземпляр объекта не будет никаких ссылок ...

Вы неправы. Технически у вас нет ссылки на созданный объект, он может быть указан в следующей строке.

Рассмотрим этот простой пример:

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
Sinatr 6 Сен 2016 в 15:39

Вы не можете сказать, действительна ли WeakReference. Но вы можете сказать, недействительна ли она. Я знаю, что это странно. Если у меня есть код, который выполняет оператор if и может спросить «действителен ли он», тогда он может оказаться недействительным в следующей строке кода, поэтому он бесполезен. Вызов TryGetTarget в WeakReference получает ссылку на объект или завершается ошибкой и возвращает false. Как только у него есть ссылка, она предотвращает сборку мусора для объекта, поэтому он будет оставаться действительным, по крайней мере, пока у вас есть ссылка.

В некотором коде можно сохранить List >

Отличный способ отслеживать этот список и очищать его от ссылок, на которые нет ссылок (в другом месте), - это выполнить сканирование списка с помощью цикла 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!

0
Richard Keene 16 Апр 2019 в 01:09