Почему WeakReference бесполезен в деструкторе?

Рассмотрим следующий код:

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? А еще не было собрано.

РЕДАКТИРОВАТЬ : О, вы мало веры.

Я добавил следующие строки:

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

См. Обновленный вывод.

10
задан MatthewMartin 17 October 2012 в 21:37
поделиться

5 ответов

Обновленный ответ на обновленный вопрос.

С новым вопросом мы проходим следующие шаги.

  1. A и B живы и укоренены, B.a живы и укоренены через B.
  2. A живы, B не укоренены и могут быть собраны. B.a не имеет корневых сертификатов и не соответствует требованиям.
  3. Сбор происходит. B и B.a могут быть финализированы, поэтому они помещаются в очередь финализатора. B не взимается, потому что он может быть завершен. B.a не собирается, и , потому что он может быть завершен, и потому, что на него ссылается B, который еще не был завершен.
  4. Либо B.a завершена, либо B завершена.
  5. Другой вариант B.a или B завершен.
  6. B.a и B имеют право на взыскание.

(Если B был завершен в пункте 4, его можно было бы собрать до пункта 5, поскольку пока B ожидает завершения, удерживает B и B.a от сбора, B.a, ожидающий завершения, не влияет на сбор B).

Произошло то, что порядок между 4 и 5 был таким, что B.a был завершен, а затем B был завершен. Поскольку ссылка, которую WeakReference содержит на объект, не является нормальной ссылкой, ему нужен собственный код очистки, чтобы освободить свой GCHandle. Очевидно, это не может зависеть от нормального поведения сборки GC, поскольку вся суть его ссылок состоит в том, что они не соответствуют нормальному поведению сборки GC.

Теперь финализатор B запущен, но поскольку поведение финализатора B.a заключалось в том, чтобы освободить ссылку, он возвращает false для IsAlive (или в .NET до 1.1, если я правильно помню версии, выдает ошибку).

3
ответ дан 4 December 2019 в 01:55
поделиться

Сборщик мусора определил, что a не работает, потому что на него больше не ссылаются после GC.collect (). Если вы измените код на:

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

, вы обнаружите a живым во время завершения B.

1
ответ дан 4 December 2019 в 01:55
поделиться

Ключевая проблема в том, что вы обращаетесь к справочному полю во время финализатора. Проблема , лежащая в основе , заключается в том, что сама 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);
    }
}

Почему вы говорите, что он не собран? Он выглядит подходящим .... ни одно поле на живом объекте не содержит его, и переменная никогда не читается после этой точки (и действительно, эта переменная вполне могла быть оптимизирована компилятором, поэтому нет "local" "в ИЛ).

Вам может понадобиться GC.KeepAlive (a) внизу Main , чтобы остановить его.

2
ответ дан 4 December 2019 в 01:55
поделиться

Почему это ложь и ноль? А еще не собрано.

Вы этого точно не знаете. GC может собирать его, как только он больше не нужен, что в данном случае происходит сразу после того, как он помещается в WeakReference.

Между прочим, у Раймонда Чена недавно была запись в блоге на эту самую тему.

1
ответ дан 4 December 2019 в 01:55
поделиться

Это действительно немного странно, и я не могу сказать, что у меня есть ответ, но вот что я нашел до сих пор. В вашем примере я прикрепил WinDbg непосредственно перед вызовом GC.Collect . На этом этапе слабая ссылка удерживается на экземпляре, как и ожидалось.

После этого я откопал реальный экземпляр WeakReference и установил точку останова по данным на самой ссылке. Исходя из этого момента, отладчик прерывается на mscorwks! WKS :: FreeWeakHandle + 0x12 (который устанавливает дескриптор в ноль), а стек управляемых вызовов выглядит следующим образом:

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] 

Похоже, это указывает на то, что вызов to GC.Collect , в свою очередь, также модифицирует слабую ссылку. Это объяснило бы наблюдаемое поведение, но я не могу сказать, будет ли оно таким во всех случаях.

2
ответ дан 4 December 2019 в 01:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: