Рассмотрим следующий код:
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);
См. Обновленный вывод.
Обновленный ответ на обновленный вопрос.
С новым вопросом мы проходим следующие шаги.
(Если 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, если я правильно помню версии, выдает ошибку).
Сборщик мусора определил, что a
не работает, потому что на него больше не ссылаются после GC.collect (). Если вы измените код на:
GC.Collect();
GC.WaitForPendingFinalizers();
System.Console.WriteLine("And here's:"+a);
, вы обнаружите a
живым во время завершения B.
Ключевая проблема в том, что вы обращаетесь к справочному полю во время финализатора. Проблема , лежащая в основе , заключается в том, что сама 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
, чтобы остановить его.
Почему это ложь и ноль? А еще не собрано.
Вы этого точно не знаете. GC может собирать его, как только он больше не нужен, что в данном случае происходит сразу после того, как он помещается в WeakReference.
Между прочим, у Раймонда Чена недавно была запись в блоге на эту самую тему.
Это действительно немного странно, и я не могу сказать, что у меня есть ответ, но вот что я нашел до сих пор. В вашем примере я прикрепил 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
, в свою очередь, также модифицирует слабую ссылку. Это объяснило бы наблюдаемое поведение, но я не могу сказать, будет ли оно таким во всех случаях.